diff --git a/packages/kbn-test/src/jest/utils/get_url.ts b/packages/kbn-test/src/jest/utils/get_url.ts
index e08695b334e1b..734e26c5199d7 100644
--- a/packages/kbn-test/src/jest/utils/get_url.ts
+++ b/packages/kbn-test/src/jest/utils/get_url.ts
@@ -22,6 +22,11 @@ interface UrlParam {
username?: string;
}
+interface App {
+ pathname?: string;
+ hash?: string;
+}
+
/**
* Converts a config and a pathname to a url
* @param {object} config A url config
@@ -41,11 +46,11 @@ interface UrlParam {
* @return {string}
*/
-function getUrl(config: UrlParam, app: UrlParam) {
+function getUrl(config: UrlParam, app: App) {
return url.format(_.assign({}, config, app));
}
-getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: UrlParam) {
+getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: App) {
config = _.pickBy(config, function (val, param) {
return param !== 'auth';
});
diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts
index 8a2a66c41d426..49d56d6f43784 100644
--- a/test/functional/page_objects/common_page.ts
+++ b/test/functional/page_objects/common_page.ts
@@ -202,13 +202,7 @@ export class CommonPageObject extends FtrService {
async navigateToApp(
appName: string,
- {
- basePath = '',
- shouldLoginIfPrompted = true,
- hash = '',
- search = '',
- insertTimestamp = true,
- } = {}
+ { basePath = '', shouldLoginIfPrompted = true, hash = '', insertTimestamp = true } = {}
) {
let appUrl: string;
if (this.config.has(['apps', appName])) {
@@ -217,13 +211,11 @@ export class CommonPageObject extends FtrService {
appUrl = getUrl.noAuth(this.config.get('servers.kibana'), {
pathname: `${basePath}${appConfig.pathname}`,
hash: hash || appConfig.hash,
- search,
});
} else {
appUrl = getUrl.noAuth(this.config.get('servers.kibana'), {
pathname: `${basePath}/app/${appName}`,
hash,
- search,
});
}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
index 0bd873bd7064b..4e6544a20f301 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
@@ -11,11 +11,11 @@ import { i18n } from '@kbn/i18n';
import {
createExploratoryViewUrl,
HeaderMenuPortal,
+ SeriesUrl,
} from '../../../../../../observability/public';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { AppMountParameters } from '../../../../../../../../src/core/public';
-import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames';
const ANALYZE_DATA = i18n.translate('xpack.apm.analyzeDataButtonLabel', {
defaultMessage: 'Analyze data',
@@ -38,22 +38,15 @@ export function UXActionMenu({
services: { http },
} = useKibana();
const { urlParams } = useUrlParams();
- const { rangeTo, rangeFrom, serviceName } = urlParams;
+ const { rangeTo, rangeFrom } = urlParams;
const uxExploratoryViewLink = createExploratoryViewUrl(
{
- reportType: 'kpi-over-time',
- allSeries: [
- {
- dataType: 'ux',
- name: `${serviceName}-page-views`,
- time: { from: rangeFrom!, to: rangeTo! },
- reportDefinitions: {
- [SERVICE_NAME]: serviceName ? [serviceName] : [],
- },
- selectedMetricField: 'Records',
- },
- ],
+ 'ux-series': ({
+ dataType: 'ux',
+ isNew: true,
+ time: { from: rangeFrom, to: rangeTo },
+ } as unknown) as SeriesUrl,
},
http?.basePath.get()
);
@@ -67,7 +60,6 @@ export function UXActionMenu({
{ANALYZE_MESSAGE}
}>
{
render();
expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
- 'http://localhost/app/observability/exploratory-view/configure#?reportType=kpi-over-time&sr=!((dt:ux,mt:transaction.duration.us,n:testServiceName-response-latency,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),time:(from:now-15m,to:now)))'
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:ux,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
);
});
});
@@ -48,7 +48,7 @@ describe('AnalyzeDataButton', () => {
render();
expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
- 'http://localhost/app/observability/exploratory-view/configure#?reportType=kpi-over-time&sr=!((dt:mobile,mt:transaction.duration.us,n:testServiceName-response-latency,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),time:(from:now-15m,to:now)))'
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
);
});
});
@@ -58,7 +58,7 @@ describe('AnalyzeDataButton', () => {
render();
expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
- 'http://localhost/app/observability/exploratory-view/configure#?reportType=kpi-over-time&sr=!((dt:mobile,mt:transaction.duration.us,n:testServiceName-response-latency,op:average,rdf:(service.name:!(testServiceName)),time:(from:now-15m,to:now)))'
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
);
});
});
@@ -68,7 +68,7 @@ describe('AnalyzeDataButton', () => {
render();
expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
- 'http://localhost/app/observability/exploratory-view/configure#?reportType=kpi-over-time&sr=!((dt:mobile,mt:transaction.duration.us,n:testServiceName-response-latency,op:average,rdf:(service.environment:!(ENVIRONMENT_NOT_DEFINED),service.name:!(testServiceName)),time:(from:now-15m,to:now)))'
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
);
});
});
@@ -78,7 +78,7 @@ describe('AnalyzeDataButton', () => {
render();
expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
- 'http://localhost/app/observability/exploratory-view/configure#?reportType=kpi-over-time&sr=!((dt:mobile,mt:transaction.duration.us,n:testServiceName-response-latency,op:average,rdf:(service.environment:!(ALL_VALUES),service.name:!(testServiceName)),time:(from:now-15m,to:now)))'
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(ALL_VALUES),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
);
});
});
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx
index 5af6ea6cdc777..d8ff7fdf47c58 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx
@@ -9,7 +9,10 @@ import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
-import { createExploratoryViewUrl } from '../../../../../../observability/public';
+import {
+ createExploratoryViewUrl,
+ SeriesUrl,
+} from '../../../../../../observability/public';
import { ALL_VALUES_SELECTED } from '../../../../../../observability/public';
import {
isIosAgentName,
@@ -18,7 +21,6 @@ import {
import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
- TRANSACTION_DURATION,
} from '../../../../../common/elasticsearch_fieldnames';
import {
ENVIRONMENT_ALL,
@@ -27,11 +29,13 @@ import {
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
-function getEnvironmentDefinition(environment: string) {
+function getEnvironmentDefinition(environment?: string) {
switch (environment) {
case ENVIRONMENT_ALL.value:
return { [SERVICE_ENVIRONMENT]: [ALL_VALUES_SELECTED] };
case ENVIRONMENT_NOT_DEFINED.value:
+ case undefined:
+ return {};
default:
return { [SERVICE_ENVIRONMENT]: [environment] };
}
@@ -47,26 +51,21 @@ export function AnalyzeDataButton() {
if (
(isRumAgentName(agentName) || isIosAgentName(agentName)) &&
- rangeFrom &&
- canShowDashboard &&
- rangeTo
+ canShowDashboard
) {
const href = createExploratoryViewUrl(
{
- reportType: 'kpi-over-time',
- allSeries: [
- {
- name: `${serviceName}-response-latency`,
- selectedMetricField: TRANSACTION_DURATION,
- dataType: isRumAgentName(agentName) ? 'ux' : 'mobile',
- time: { from: rangeFrom, to: rangeTo },
- reportDefinitions: {
- [SERVICE_NAME]: [serviceName],
- ...(environment ? getEnvironmentDefinition(environment) : {}),
- },
- operationType: 'average',
+ 'apm-series': {
+ dataType: isRumAgentName(agentName) ? 'ux' : 'mobile',
+ time: { from: rangeFrom, to: rangeTo },
+ reportType: 'kpi-over-time',
+ reportDefinitions: {
+ [SERVICE_NAME]: [serviceName],
+ ...getEnvironmentDefinition(environment),
},
- ],
+ operationType: 'average',
+ isNew: true,
+ } as SeriesUrl,
},
basepath
);
diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
index 70585f9a9bf28..28fab3369b1eb 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import moment from 'moment';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import {
SERVICE_NAME,
@@ -21,10 +20,7 @@ export async function hasRumData({
setup: Setup & Partial;
}) {
try {
- const {
- start = moment().subtract(24, 'h').valueOf(),
- end = moment().valueOf(),
- } = setup;
+ const { start, end } = setup;
const params = {
apm: {
diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json
index b794f91231505..4273252850da4 100644
--- a/x-pack/plugins/observability/kibana.json
+++ b/x-pack/plugins/observability/kibana.json
@@ -6,8 +6,16 @@
},
"version": "8.0.0",
"kibanaVersion": "kibana",
- "configPath": ["xpack", "observability"],
- "optionalPlugins": ["home", "discover", "lens", "licensing", "usageCollection"],
+ "configPath": [
+ "xpack",
+ "observability"
+ ],
+ "optionalPlugins": [
+ "home",
+ "lens",
+ "licensing",
+ "usageCollection"
+ ],
"requiredPlugins": [
"alerting",
"cases",
diff --git a/x-pack/plugins/observability/public/components/shared/add_data_buttons/mobile_add_data.tsx b/x-pack/plugins/observability/public/components/shared/add_data_buttons/mobile_add_data.tsx
deleted file mode 100644
index 0e17c6277618b..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/add_data_buttons/mobile_add_data.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiHeaderLink } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { useKibana } from '../../../utils/kibana_react';
-
-export function MobileAddData() {
- const kibana = useKibana();
-
- return (
-
- {ADD_DATA_LABEL}
-
- );
-}
-
-const ADD_DATA_LABEL = i18n.translate('xpack.observability.mobile.addDataButtonLabel', {
- defaultMessage: 'Add Mobile data',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx b/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx
deleted file mode 100644
index af91624769e6b..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiHeaderLink } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { useKibana } from '../../../utils/kibana_react';
-
-export function SyntheticsAddData() {
- const kibana = useKibana();
-
- return (
-
- {ADD_DATA_LABEL}
-
- );
-}
-
-const ADD_DATA_LABEL = i18n.translate('xpack.observability..synthetics.addDataButtonLabel', {
- defaultMessage: 'Add synthetics data',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/add_data_buttons/ux_add_data.tsx b/x-pack/plugins/observability/public/components/shared/add_data_buttons/ux_add_data.tsx
deleted file mode 100644
index c6aa0742466f1..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/add_data_buttons/ux_add_data.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiHeaderLink } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { useKibana } from '../../../utils/kibana_react';
-
-export function UXAddData() {
- const kibana = useKibana();
-
- return (
-
- {ADD_DATA_LABEL}
-
- );
-}
-
-const ADD_DATA_LABEL = i18n.translate('xpack.observability.ux.addDataButtonLabel', {
- defaultMessage: 'Add UX data',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx
deleted file mode 100644
index 329192abc99d2..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx
+++ /dev/null
@@ -1,59 +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 { render } from '../../rtl_helpers';
-import { fireEvent, screen } from '@testing-library/dom';
-import React from 'react';
-import { sampleAttribute } from '../../configurations/test_data/sample_attribute';
-import * as pluginHook from '../../../../../hooks/use_plugin_context';
-import { TypedLensByValueInput } from '../../../../../../../lens/public';
-import { ExpViewActionMenuContent } from './action_menu';
-
-jest.spyOn(pluginHook, 'usePluginContext').mockReturnValue({
- appMountParameters: {
- setHeaderActionMenu: jest.fn(),
- },
-} as any);
-
-describe('Action Menu', function () {
- it('should be able to click open in lens', async function () {
- const { findByText, core } = render(
-
- );
-
- expect(await screen.findByText('Open in Lens')).toBeInTheDocument();
-
- fireEvent.click(await findByText('Open in Lens'));
-
- expect(core.lens?.navigateToPrefilledEditor).toHaveBeenCalledTimes(1);
- expect(core.lens?.navigateToPrefilledEditor).toHaveBeenCalledWith(
- {
- id: '',
- attributes: sampleAttribute,
- timeRange: { to: 'now', from: 'now-10m' },
- },
- true
- );
- });
-
- it('should be able to click save', async function () {
- const { findByText } = render(
-
- );
-
- expect(await screen.findByText('Save')).toBeInTheDocument();
-
- fireEvent.click(await findByText('Save'));
-
- expect(await screen.findByText('Lens Save Modal Component')).toBeInTheDocument();
- });
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx
deleted file mode 100644
index 38011eb5f8ffb..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx
+++ /dev/null
@@ -1,92 +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, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { LensEmbeddableInput, TypedLensByValueInput } from '../../../../../../../lens/public';
-import { ObservabilityAppServices } from '../../../../../application/types';
-import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
-
-export function ExpViewActionMenuContent({
- timeRange,
- lensAttributes,
-}: {
- timeRange?: { from: string; to: string };
- lensAttributes: TypedLensByValueInput['attributes'] | null;
-}) {
- const kServices = useKibana().services;
-
- const { lens } = kServices;
-
- const [isSaveOpen, setIsSaveOpen] = useState(false);
-
- const LensSaveModalComponent = lens.SaveModalComponent;
-
- return (
- <>
-
-
- {
- if (lensAttributes) {
- lens.navigateToPrefilledEditor(
- {
- id: '',
- timeRange,
- attributes: lensAttributes,
- },
- true
- );
- }
- }}
- >
- {i18n.translate('xpack.observability.expView.heading.openInLens', {
- defaultMessage: 'Open in Lens',
- })}
-
-
-
- {
- if (lensAttributes) {
- setIsSaveOpen(true);
- }
- }}
- size="s"
- >
- {i18n.translate('xpack.observability.expView.heading.saveLensVisualization', {
- defaultMessage: 'Save',
- })}
-
-
-
-
- {isSaveOpen && lensAttributes && (
- setIsSaveOpen(false)}
- // if we want to do anything after the viz is saved
- // right now there is no action, so an empty function
- onSave={() => {}}
- />
- )}
- >
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/index.tsx
deleted file mode 100644
index 23500b63e900a..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/index.tsx
+++ /dev/null
@@ -1,26 +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 { ExpViewActionMenuContent } from './action_menu';
-import HeaderMenuPortal from '../../../header_menu_portal';
-import { usePluginContext } from '../../../../../hooks/use_plugin_context';
-import { TypedLensByValueInput } from '../../../../../../../lens/public';
-
-interface Props {
- timeRange?: { from: string; to: string };
- lensAttributes: TypedLensByValueInput['attributes'] | null;
-}
-export function ExpViewActionMenu(props: Props) {
- const { appMountParameters } = usePluginContext();
-
- return (
-
-
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx
index d17e451ef702c..3566835b1701c 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx
@@ -10,19 +10,19 @@ import { isEmpty } from 'lodash';
import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiText } from '@elastic/eui';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
-import { LOADING_VIEW } from '../series_editor/series_editor';
-import { ReportViewType, SeriesUrl } from '../types';
+import { LOADING_VIEW } from '../series_builder/series_builder';
+import { SeriesUrl } from '../types';
export function EmptyView({
loading,
+ height,
series,
- reportType,
}: {
loading: boolean;
- series?: SeriesUrl;
- reportType: ReportViewType;
+ height: string;
+ series: SeriesUrl;
}) {
- const { dataType, reportDefinitions } = series ?? {};
+ const { dataType, reportType, reportDefinitions } = series ?? {};
let emptyMessage = EMPTY_LABEL;
@@ -45,7 +45,7 @@ export function EmptyView({
}
return (
-
+
{loading && (
`
text-align: center;
+ height: ${(props) => props.height};
position: relative;
`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
index 03fd23631f755..fe2953edd36d6 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
-import { mockAppIndexPattern, mockIndexPattern, mockUxSeries, render } from '../rtl_helpers';
+import { mockAppIndexPattern, mockIndexPattern, render } from '../rtl_helpers';
import { FilterLabel } from './filter_label';
import * as useSeriesHook from '../hooks/use_series_filters';
import { buildFilterLabel } from '../../filter_value_label/filter_value_label';
@@ -27,10 +27,9 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={0}
+ seriesId={'kpi-over-time'}
removeFilter={jest.fn()}
indexPattern={mockIndexPattern}
- series={mockUxSeries}
/>
);
@@ -52,10 +51,9 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={0}
+ seriesId={'kpi-over-time'}
removeFilter={removeFilter}
indexPattern={mockIndexPattern}
- series={mockUxSeries}
/>
);
@@ -76,10 +74,9 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={0}
+ seriesId={'kpi-over-time'}
removeFilter={removeFilter}
indexPattern={mockIndexPattern}
- series={mockUxSeries}
/>
);
@@ -103,10 +100,9 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={true}
- seriesId={0}
+ seriesId={'kpi-over-time'}
removeFilter={jest.fn()}
indexPattern={mockIndexPattern}
- series={mockUxSeries}
/>
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx
index c6254a85de9ac..a08e777c5ea71 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.tsx
@@ -9,24 +9,21 @@ import React from 'react';
import { IndexPattern } from '../../../../../../../../src/plugins/data/public';
import { useSeriesFilters } from '../hooks/use_series_filters';
import { FilterValueLabel } from '../../filter_value_label/filter_value_label';
-import { SeriesUrl } from '../types';
interface Props {
field: string;
label: string;
- value: string | string[];
- seriesId: number;
- series: SeriesUrl;
+ value: string;
+ seriesId: string;
negate: boolean;
definitionFilter?: boolean;
indexPattern: IndexPattern;
- removeFilter: (field: string, value: string | string[], notVal: boolean) => void;
+ removeFilter: (field: string, value: string, notVal: boolean) => void;
}
export function FilterLabel({
label,
seriesId,
- series,
field,
value,
negate,
@@ -34,7 +31,7 @@ export function FilterLabel({
removeFilter,
definitionFilter,
}: Props) {
- const { invertFilter } = useSeriesFilters({ seriesId, series });
+ const { invertFilter } = useSeriesFilters({ seriesId });
return indexPattern ? (
{
- setSeries(seriesId, { ...series, color: colorN });
- };
-
- const color =
- series.color ?? ((theme.eui as unknown) as Record)[`euiColorVis${seriesId}`];
-
- const button = (
-
- setIsOpen((prevState) => !prevState)} hasArrow={false}>
-
-
-
- );
-
- return (
- setIsOpen(false)}>
-
-
-
-
- );
-}
-
-const PICK_A_COLOR_LABEL = i18n.translate(
- 'xpack.observability.overview.exploratoryView.pickColor',
- {
- defaultMessage: 'Pick a color',
- }
-);
-
-const EDIT_SERIES_COLOR_LABEL = i18n.translate(
- 'xpack.observability.overview.exploratoryView.editSeriesColor',
- {
- defaultMessage: 'Edit color for series',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/series_date_picker/index.tsx
deleted file mode 100644
index 23d6589fecbcb..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/series_date_picker/index.tsx
+++ /dev/null
@@ -1,109 +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 moment from 'moment';
-import { EuiSuperDatePicker, EuiText } from '@elastic/eui';
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-
-import { useHasData } from '../../../../../hooks/use_has_data';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { useQuickTimeRanges } from '../../../../../hooks/use_quick_time_ranges';
-import { parseTimeParts } from '../../series_viewer/columns/utils';
-import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public';
-import { SeriesUrl } from '../../types';
-import { ReportTypes } from '../../configurations/constants';
-
-export interface TimePickerTime {
- from: string;
- to: string;
-}
-
-export interface TimePickerQuickRange extends TimePickerTime {
- display: string;
-}
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- readonly?: boolean;
-}
-const readableUnit: Record = {
- m: i18n.translate('xpack.observability.overview.exploratoryView.minutes', {
- defaultMessage: 'Minutes',
- }),
- h: i18n.translate('xpack.observability.overview.exploratoryView.hour', {
- defaultMessage: 'Hour',
- }),
- d: i18n.translate('xpack.observability.overview.exploratoryView.day', {
- defaultMessage: 'Day',
- }),
-};
-
-export function SeriesDatePicker({ series, seriesId, readonly = true }: Props) {
- const { onRefreshTimeRange } = useHasData();
-
- const commonlyUsedRanges = useQuickTimeRanges();
-
- const { setSeries, reportType, allSeries, firstSeries } = useSeriesStorage();
-
- function onTimeChange({ start, end }: { start: string; end: string }) {
- onRefreshTimeRange();
- if (reportType === ReportTypes.KPI) {
- allSeries.forEach((currSeries, seriesIndex) => {
- setSeries(seriesIndex, { ...currSeries, time: { from: start, to: end } });
- });
- } else {
- setSeries(seriesId, { ...series, time: { from: start, to: end } });
- }
- }
-
- const seriesTime = series.time ?? firstSeries!.time;
-
- const dateFormat = useUiSetting('dateFormat').replace('ss.SSS', 'ss');
-
- if (readonly) {
- const timeParts = parseTimeParts(seriesTime?.from, seriesTime?.to);
-
- if (timeParts) {
- const {
- timeTense: timeTenseDefault,
- timeUnits: timeUnitsDefault,
- timeValue: timeValueDefault,
- } = timeParts;
-
- return (
- {`${timeTenseDefault} ${timeValueDefault} ${
- readableUnit?.[timeUnitsDefault] ?? timeUnitsDefault
- }`}
- );
- } else {
- return (
-
- {i18n.translate('xpack.observability.overview.exploratoryView.dateRangeReadonly', {
- defaultMessage: '{start} to {end}',
- values: {
- start: moment(seriesTime.from).format(dateFormat),
- end: moment(seriesTime.to).format(dateFormat),
- },
- })}
-
- );
- }
- }
-
- return (
-
- );
-}
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 bf5feb7d5863c..ba1f2214223e3 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
@@ -94,19 +94,6 @@ export const DataViewLabels: Record = {
'device-data-distribution': DEVICE_DISTRIBUTION_LABEL,
};
-export enum ReportTypes {
- KPI = 'kpi-over-time',
- DISTRIBUTION = 'data-distribution',
- CORE_WEB_VITAL = 'core-web-vitals',
- DEVICE_DISTRIBUTION = 'device-data-distribution',
-}
-
-export enum DataTypes {
- SYNTHETICS = 'synthetics',
- UX = 'ux',
- MOBILE = 'mobile',
-}
-
export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
export const FILTER_RECORDS = 'FILTER_RECORDS';
export const TERMS_COLUMN = 'TERMS_COLUMN';
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 55ac75b47c056..6f990015fbc62 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
@@ -8,12 +8,10 @@
export enum URL_KEYS {
DATA_TYPE = 'dt',
OPERATION_TYPE = 'op',
+ REPORT_TYPE = 'rt',
SERIES_TYPE = 'st',
BREAK_DOWN = 'bd',
FILTERS = 'ft',
REPORT_DEFINITIONS = 'rdf',
SELECTED_METRIC = 'mt',
- HIDDEN = 'h',
- NAME = 'n',
- COLOR = 'c',
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
index 3f6551986527c..574a9f6a2bc10 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
@@ -15,7 +15,6 @@ import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config';
import { getMobileKPIConfig } from './mobile/kpi_over_time_config';
import { getMobileKPIDistributionConfig } from './mobile/distribution_config';
import { getMobileDeviceDistributionConfig } from './mobile/device_distribution_config';
-import { DataTypes, ReportTypes } from './constants';
interface Props {
reportType: ReportViewType;
@@ -25,24 +24,24 @@ interface Props {
export const getDefaultConfigs = ({ reportType, dataType, indexPattern }: Props) => {
switch (dataType) {
- case DataTypes.UX:
- if (reportType === ReportTypes.DISTRIBUTION) {
+ case 'ux':
+ if (reportType === 'data-distribution') {
return getRumDistributionConfig({ indexPattern });
}
- if (reportType === ReportTypes.CORE_WEB_VITAL) {
+ if (reportType === 'core-web-vitals') {
return getCoreWebVitalsConfig({ indexPattern });
}
return getKPITrendsLensConfig({ indexPattern });
- case DataTypes.SYNTHETICS:
- if (reportType === ReportTypes.DISTRIBUTION) {
+ case 'synthetics':
+ if (reportType === 'data-distribution') {
return getSyntheticsDistributionConfig({ indexPattern });
}
return getSyntheticsKPIConfig({ indexPattern });
- case DataTypes.MOBILE:
- if (reportType === ReportTypes.DISTRIBUTION) {
+ case 'mobile':
+ if (reportType === 'data-distribution') {
return getMobileKPIDistributionConfig({ indexPattern });
}
- if (reportType === ReportTypes.DEVICE_DISTRIBUTION) {
+ if (reportType === 'device-data-distribution') {
return getMobileDeviceDistributionConfig({ indexPattern });
}
return getMobileKPIConfig({ indexPattern });
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 08d2da4714e47..ae70bbdcfa3b8 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
@@ -16,7 +16,7 @@ import {
} from './constants/elasticsearch_fieldnames';
import { buildExistsFilter, buildPhrasesFilter } from './utils';
import { sampleAttributeKpi } from './test_data/sample_attribute_kpi';
-import { RECORDS_FIELD, REPORT_METRIC_FIELD, ReportTypes } from './constants';
+import { REPORT_METRIC_FIELD } from './constants';
describe('Lens Attribute', () => {
mockAppIndexPattern();
@@ -38,9 +38,6 @@ describe('Lens Attribute', () => {
indexPattern: mockIndexPattern,
reportDefinitions: {},
time: { from: 'now-15m', to: 'now' },
- color: 'green',
- name: 'test-series',
- selectedMetricField: TRANSACTION_DURATION,
};
beforeEach(() => {
@@ -53,7 +50,7 @@ describe('Lens Attribute', () => {
it('should return expected json for kpi report type', function () {
const seriesConfigKpi = getDefaultConfigs({
- reportType: ReportTypes.KPI,
+ reportType: 'kpi-over-time',
dataType: 'ux',
indexPattern: mockIndexPattern,
});
@@ -66,9 +63,6 @@ describe('Lens Attribute', () => {
indexPattern: mockIndexPattern,
reportDefinitions: { 'service.name': ['elastic-co'] },
time: { from: 'now-15m', to: 'now' },
- color: 'green',
- name: 'test-series',
- selectedMetricField: RECORDS_FIELD,
},
]);
@@ -141,9 +135,6 @@ describe('Lens Attribute', () => {
indexPattern: mockIndexPattern,
reportDefinitions: { 'performance.metric': [LCP_FIELD] },
time: { from: 'now-15m', to: 'now' },
- color: 'green',
- name: 'test-series',
- selectedMetricField: TRANSACTION_DURATION,
};
lnsAttr = new LensAttributes([layerConfig1]);
@@ -286,7 +277,7 @@ describe('Lens Attribute', () => {
'transaction.type: page-load and processor.event: transaction and transaction.type : *',
},
isBucketed: false,
- label: 'test-series',
+ label: 'Pages loaded',
operationType: 'formula',
params: {
format: {
@@ -392,7 +383,7 @@ describe('Lens Attribute', () => {
palette: undefined,
seriesType: 'line',
xAccessor: 'x-axis-column-layer0',
- yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0' }],
+ yConfig: [{ forAccessor: 'y-axis-column-layer0' }],
},
],
legend: { isVisible: true, position: 'right' },
@@ -412,9 +403,6 @@ describe('Lens Attribute', () => {
reportDefinitions: { 'performance.metric': [LCP_FIELD] },
breakdown: USER_AGENT_NAME,
time: { from: 'now-15m', to: 'now' },
- color: 'green',
- name: 'test-series',
- selectedMetricField: TRANSACTION_DURATION,
};
lnsAttr = new LensAttributes([layerConfig1]);
@@ -434,7 +422,7 @@ describe('Lens Attribute', () => {
seriesType: 'line',
splitAccessor: 'breakdown-column-layer0',
xAccessor: 'x-axis-column-layer0',
- yConfig: [{ color: 'green', forAccessor: 'y-axis-column-layer0' }],
+ yConfig: [{ forAccessor: 'y-axis-column-layer0' }],
},
]);
@@ -495,7 +483,7 @@ describe('Lens Attribute', () => {
'transaction.type: page-load and processor.event: transaction and transaction.type : *',
},
isBucketed: false,
- label: 'test-series',
+ label: 'Pages loaded',
operationType: 'formula',
params: {
format: {
@@ -601,9 +589,6 @@ describe('Lens Attribute', () => {
indexPattern: mockIndexPattern,
reportDefinitions: { 'performance.metric': [LCP_FIELD] },
time: { from: 'now-15m', to: 'now' },
- color: 'green',
- name: 'test-series',
- selectedMetricField: TRANSACTION_DURATION,
};
const filters = lnsAttr.getLayerFilters(layerConfig1, 2);
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 5426d3bcd4233..dfb17ee470d35 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
@@ -7,7 +7,6 @@
import { i18n } from '@kbn/i18n';
import { capitalize } from 'lodash';
-
import {
CountIndexPatternColumn,
DateHistogramIndexPatternColumn,
@@ -37,11 +36,10 @@ import {
REPORT_METRIC_FIELD,
RECORDS_FIELD,
RECORDS_PERCENTAGE_FIELD,
- ReportTypes,
} from './constants';
import { ColumnFilter, SeriesConfig, UrlFilter, URLReportDefinition } from '../types';
import { PersistableFilter } from '../../../../../../lens/common';
-import { parseAbsoluteDate } from '../components/date_range_picker';
+import { parseAbsoluteDate } from '../series_date_picker/date_range_picker';
import { getDistributionInPercentageColumn } from './lens_columns/overall_column';
function getLayerReferenceName(layerId: string) {
@@ -75,6 +73,14 @@ export const parseCustomFieldName = (seriesConfig: SeriesConfig, selectedMetricF
timeScale = currField?.timeScale;
columnLabel = currField?.label;
}
+ } else if (metricOptions?.[0].field || metricOptions?.[0].id) {
+ const firstMetricOption = metricOptions?.[0];
+
+ selectedMetricField = firstMetricOption.field || firstMetricOption.id;
+ columnType = firstMetricOption.columnType;
+ columnFilters = firstMetricOption.columnFilters;
+ timeScale = firstMetricOption.timeScale;
+ columnLabel = firstMetricOption.label;
}
return { fieldName: selectedMetricField!, columnType, columnFilters, timeScale, columnLabel };
@@ -89,9 +95,7 @@ export interface LayerConfig {
reportDefinitions: URLReportDefinition;
time: { to: string; from: string };
indexPattern: IndexPattern;
- selectedMetricField: string;
- color: string;
- name: string;
+ selectedMetricField?: string;
}
export class LensAttributes {
@@ -467,15 +471,14 @@ export class LensAttributes {
getLayerFilters(layerConfig: LayerConfig, totalLayers: number) {
const {
filters,
- time,
+ time: { from, to },
seriesConfig: { baseFilters: layerFilters, reportType },
} = layerConfig;
let baseFilters = '';
-
- if (reportType !== ReportTypes.KPI && totalLayers > 1 && time) {
+ if (reportType !== 'kpi-over-time' && totalLayers > 1) {
// for kpi over time, we don't need to add time range filters
// since those are essentially plotted along the x-axis
- baseFilters += `@timestamp >= ${time.from} and @timestamp <= ${time.to}`;
+ baseFilters += `@timestamp >= ${from} and @timestamp <= ${to}`;
}
layerFilters?.forEach((filter: PersistableFilter | ExistsFilter) => {
@@ -531,11 +534,7 @@ export class LensAttributes {
}
getTimeShift(mainLayerConfig: LayerConfig, layerConfig: LayerConfig, index: number) {
- if (
- index === 0 ||
- mainLayerConfig.seriesConfig.reportType !== ReportTypes.KPI ||
- !layerConfig.time
- ) {
+ if (index === 0 || mainLayerConfig.seriesConfig.reportType !== 'kpi-over-time') {
return null;
}
@@ -547,14 +546,11 @@ export class LensAttributes {
time: { from },
} = layerConfig;
- const inDays = Math.abs(parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'days'));
+ const inDays = parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'days');
if (inDays > 1) {
return inDays + 'd';
}
- const inHours = Math.abs(parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'hours'));
- if (inHours === 0) {
- return null;
- }
+ const inHours = parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'hours');
return inHours + 'h';
}
@@ -572,12 +568,6 @@ export class LensAttributes {
const { sourceField } = seriesConfig.xAxisColumn;
- let label = timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label;
-
- if (layerConfig.seriesConfig.reportType !== ReportTypes.CORE_WEB_VITAL && layerConfig.name) {
- label = layerConfig.name;
- }
-
layers[layerId] = {
columnOrder: [
`x-axis-column-${layerId}`,
@@ -591,7 +581,7 @@ export class LensAttributes {
[`x-axis-column-${layerId}`]: this.getXAxis(layerConfig, layerId),
[`y-axis-column-${layerId}`]: {
...mainYAxis,
- label,
+ label: timeShift ? `${mainYAxis.label}(${timeShift})` : mainYAxis.label,
filter: { query: columnFilter, language: 'kuery' },
...(timeShift ? { timeShift } : {}),
},
@@ -634,7 +624,7 @@ export class LensAttributes {
seriesType: layerConfig.seriesType || layerConfig.seriesConfig.defaultSeriesType,
palette: layerConfig.seriesConfig.palette,
yConfig: layerConfig.seriesConfig.yConfig || [
- { forAccessor: `y-axis-column-layer${index}`, color: layerConfig.color },
+ { forAccessor: `y-axis-column-layer${index}` },
],
xAccessor: `x-axis-column-layer${index}`,
...(layerConfig.breakdown &&
@@ -648,7 +638,7 @@ export class LensAttributes {
};
}
- getJSON(refresh?: number): TypedLensByValueInput['attributes'] {
+ getJSON(): TypedLensByValueInput['attributes'] {
const uniqueIndexPatternsIds = Array.from(
new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)])
);
@@ -657,7 +647,7 @@ export class LensAttributes {
return {
title: 'Prefilled from exploratory view app',
- description: String(refresh),
+ description: '',
visualizationType: 'lnsXY',
references: [
...uniqueIndexPatternsIds.map((patternId) => ({
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
index 4e178bba7e02a..d1612a08f5551 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import { FieldLabels, REPORT_METRIC_FIELD, ReportTypes, USE_BREAK_DOWN_COLUMN } from '../constants';
+import { FieldLabels, REPORT_METRIC_FIELD, USE_BREAK_DOWN_COLUMN } from '../constants';
import { buildPhraseFilter } from '../utils';
import { SERVICE_NAME } from '../constants/elasticsearch_fieldnames';
import { MOBILE_APP, NUMBER_OF_DEVICES } from '../constants/labels';
@@ -14,7 +14,7 @@ import { MobileFields } from './mobile_fields';
export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.DEVICE_DISTRIBUTION,
+ reportType: 'device-data-distribution',
defaultSeriesType: 'bar',
seriesTypes: ['bar', 'bar_horizontal'],
xAxisColumn: {
@@ -38,13 +38,13 @@ export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps)
...MobileFields,
[SERVICE_NAME]: MOBILE_APP,
},
- definitionFields: [SERVICE_NAME],
metricOptions: [
{
- field: 'labels.device_id',
id: 'labels.device_id',
+ field: 'labels.device_id',
label: NUMBER_OF_DEVICES,
},
],
+ definitionFields: [SERVICE_NAME],
};
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
index 1da27be4fcc95..9b1c4c8da3e9b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import { FieldLabels, RECORDS_FIELD, REPORT_METRIC_FIELD, ReportTypes } from '../constants';
+import { FieldLabels, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants';
import { buildPhrasesFilter } from '../utils';
import {
METRIC_SYSTEM_CPU_USAGE,
@@ -21,7 +21,7 @@ import { MobileFields } from './mobile_fields';
export function getMobileKPIDistributionConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.DISTRIBUTION,
+ reportType: 'data-distribution',
defaultSeriesType: 'bar',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
index 3ee5b3125fcda..945a631078a33 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts
@@ -6,13 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import {
- FieldLabels,
- OPERATION_COLUMN,
- RECORDS_FIELD,
- REPORT_METRIC_FIELD,
- ReportTypes,
-} from '../constants';
+import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants';
import { buildPhrasesFilter } from '../utils';
import {
METRIC_SYSTEM_CPU_USAGE,
@@ -32,7 +26,7 @@ import { MobileFields } from './mobile_fields';
export function getMobileKPIConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.KPI,
+ reportType: 'kpi-over-time',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar', 'area'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts
index 35e094996f6f2..07bb13f957e45 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts
@@ -9,7 +9,7 @@ import { mockAppIndexPattern, mockIndexPattern } from '../../rtl_helpers';
import { getDefaultConfigs } from '../default_configs';
import { LayerConfig, LensAttributes } from '../lens_attributes';
import { sampleAttributeCoreWebVital } from '../test_data/sample_attribute_cwv';
-import { LCP_FIELD, SERVICE_NAME, USER_AGENT_OS } from '../constants/elasticsearch_fieldnames';
+import { SERVICE_NAME, USER_AGENT_OS } from '../constants/elasticsearch_fieldnames';
describe('Core web vital config test', function () {
mockAppIndexPattern();
@@ -24,13 +24,10 @@ describe('Core web vital config test', function () {
const layerConfig: LayerConfig = {
seriesConfig,
- color: 'green',
- name: 'test-series',
- breakdown: USER_AGENT_OS,
indexPattern: mockIndexPattern,
- time: { from: 'now-15m', to: 'now' },
reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] },
- selectedMetricField: LCP_FIELD,
+ time: { from: 'now-15m', to: 'now' },
+ breakdown: USER_AGENT_OS,
};
beforeEach(() => {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
index e8d620388a89e..62455df248085 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
@@ -11,7 +11,6 @@ import {
FieldLabels,
FILTER_RECORDS,
REPORT_METRIC_FIELD,
- ReportTypes,
USE_BREAK_DOWN_COLUMN,
} from '../constants';
import { buildPhraseFilter } from '../utils';
@@ -39,7 +38,7 @@ export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesCon
return {
defaultSeriesType: 'bar_horizontal_percentage_stacked',
- reportType: ReportTypes.CORE_WEB_VITAL,
+ reportType: 'core-web-vitals',
seriesTypes: ['bar_horizontal_percentage_stacked'],
xAxisColumn: {
sourceField: USE_BREAK_DOWN_COLUMN,
@@ -154,6 +153,5 @@ export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): SeriesCon
{ color: statusPallete[1], forAccessor: 'y-axis-column-1' },
{ color: statusPallete[2], forAccessor: 'y-axis-column-2' },
],
- query: { query: 'transaction.type: "page-load"', language: 'kuery' },
};
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
index de6f2c67b2aeb..f34c8db6c197d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
@@ -6,12 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import {
- FieldLabels,
- REPORT_METRIC_FIELD,
- RECORDS_PERCENTAGE_FIELD,
- ReportTypes,
-} from '../constants';
+import { FieldLabels, REPORT_METRIC_FIELD, RECORDS_PERCENTAGE_FIELD } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
@@ -46,7 +41,7 @@ import {
export function getRumDistributionConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.DISTRIBUTION,
+ reportType: 'data-distribution',
defaultSeriesType: 'line',
seriesTypes: [],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
index 9112778eadaa7..5899b16d12b4f 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
@@ -6,13 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import {
- FieldLabels,
- OPERATION_COLUMN,
- RECORDS_FIELD,
- REPORT_METRIC_FIELD,
- ReportTypes,
-} from '../constants';
+import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
@@ -49,7 +43,7 @@ export function getKPITrendsLensConfig({ indexPattern }: ConfigProps): SeriesCon
return {
defaultSeriesType: 'bar_stacked',
seriesTypes: [],
- reportType: ReportTypes.KPI,
+ reportType: 'kpi-over-time',
xAxisColumn: {
sourceField: '@timestamp',
},
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
index da90f45d15201..730e742f9d8c5 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
@@ -6,12 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import {
- FieldLabels,
- REPORT_METRIC_FIELD,
- RECORDS_PERCENTAGE_FIELD,
- ReportTypes,
-} from '../constants';
+import { FieldLabels, REPORT_METRIC_FIELD, RECORDS_PERCENTAGE_FIELD } from '../constants';
import {
CLS_LABEL,
DCL_LABEL,
@@ -35,7 +30,7 @@ export function getSyntheticsDistributionConfig({
indexPattern,
}: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.DISTRIBUTION,
+ reportType: 'data-distribution',
defaultSeriesType: series?.seriesType || 'line',
seriesTypes: [],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
index 65b43a83a8fb5..4ee22181d4334 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, SeriesConfig } from '../../types';
-import { FieldLabels, OPERATION_COLUMN, REPORT_METRIC_FIELD, ReportTypes } from '../constants';
+import { FieldLabels, OPERATION_COLUMN, REPORT_METRIC_FIELD } from '../constants';
import {
CLS_LABEL,
DCL_LABEL,
@@ -30,7 +30,7 @@ const SUMMARY_DOWN = 'summary.down';
export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
- reportType: ReportTypes.KPI,
+ reportType: 'kpi-over-time',
defaultSeriesType: 'bar_stacked',
seriesTypes: [],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts
index a5898f33e0ec0..569d68ad4ebff 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts
@@ -5,18 +5,12 @@
* 2.0.
*/
export const sampleAttribute = {
- description: 'undefined',
+ title: 'Prefilled from exploratory view app',
+ description: '',
+ visualizationType: 'lnsXY',
references: [
- {
- id: 'apm-*',
- name: 'indexpattern-datasource-current-indexpattern',
- type: 'index-pattern',
- },
- {
- id: 'apm-*',
- name: 'indexpattern-datasource-layer-layer0',
- type: 'index-pattern',
- },
+ { id: 'apm-*', name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern' },
+ { id: 'apm-*', name: 'indexpattern-datasource-layer-layer0', type: 'index-pattern' },
],
state: {
datasourceStates: {
@@ -34,23 +28,17 @@ export const sampleAttribute = {
],
columns: {
'x-axis-column-layer0': {
- dataType: 'number',
- isBucketed: true,
+ sourceField: 'transaction.duration.us',
label: 'Page load time',
+ dataType: 'number',
operationType: 'range',
+ isBucketed: true,
+ scale: 'interval',
params: {
- maxBars: 'auto',
- ranges: [
- {
- from: 0,
- label: '',
- to: 1000,
- },
- ],
type: 'histogram',
+ ranges: [{ from: 0, to: 1000, label: '' }],
+ maxBars: 'auto',
},
- scale: 'interval',
- sourceField: 'transaction.duration.us',
},
'y-axis-column-layer0': {
dataType: 'number',
@@ -60,7 +48,7 @@ export const sampleAttribute = {
'transaction.type: page-load and processor.event: transaction and transaction.type : *',
},
isBucketed: false,
- label: 'test-series',
+ label: 'Pages loaded',
operationType: 'formula',
params: {
format: {
@@ -93,16 +81,16 @@ export const sampleAttribute = {
'y-axis-column-layer0X1': {
customLabel: true,
dataType: 'number',
- filter: {
- language: 'kuery',
- query:
- 'transaction.type: page-load and processor.event: transaction and transaction.type : *',
- },
isBucketed: false,
label: 'Part of count() / overall_sum(count())',
operationType: 'count',
scale: 'ratio',
sourceField: 'Records',
+ filter: {
+ language: 'kuery',
+ query:
+ 'transaction.type: page-load and processor.event: transaction and transaction.type : *',
+ },
},
'y-axis-column-layer0X2': {
customLabel: true,
@@ -153,51 +141,26 @@ export const sampleAttribute = {
},
},
},
- filters: [],
- query: {
- language: 'kuery',
- query: 'transaction.duration.us < 60000000',
- },
visualization: {
- axisTitlesVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
- curveType: 'CURVE_MONOTONE_X',
+ legend: { isVisible: true, position: 'right' },
+ valueLabels: 'hide',
fittingFunction: 'Linear',
- gridlinesVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
+ curveType: 'CURVE_MONOTONE_X',
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ preferredSeriesType: 'line',
layers: [
{
accessors: ['y-axis-column-layer0'],
layerId: 'layer0',
seriesType: 'line',
+ yConfig: [{ forAccessor: 'y-axis-column-layer0' }],
xAccessor: 'x-axis-column-layer0',
- yConfig: [
- {
- color: 'green',
- forAccessor: 'y-axis-column-layer0',
- },
- ],
},
],
- legend: {
- isVisible: true,
- position: 'right',
- },
- preferredSeriesType: 'line',
- tickLabelsVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
- valueLabels: 'hide',
},
+ query: { query: 'transaction.duration.us < 60000000', language: 'kuery' },
+ filters: [],
},
- title: 'Prefilled from exploratory view app',
- visualizationType: 'lnsXY',
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
index 425bf069cc87f..2087b85b81886 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
export const sampleAttributeCoreWebVital = {
- description: 'undefined',
+ description: '',
references: [
{
id: 'apm-*',
@@ -94,7 +94,7 @@ export const sampleAttributeCoreWebVital = {
filters: [],
query: {
language: 'kuery',
- query: 'transaction.type: "page-load"',
+ query: '',
},
visualization: {
axisTitlesVisibilitySettings: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts
index 85bafdecabde0..7f066caf66bf1 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts
@@ -5,18 +5,12 @@
* 2.0.
*/
export const sampleAttributeKpi = {
- description: 'undefined',
+ title: 'Prefilled from exploratory view app',
+ description: '',
+ visualizationType: 'lnsXY',
references: [
- {
- id: 'apm-*',
- name: 'indexpattern-datasource-current-indexpattern',
- type: 'index-pattern',
- },
- {
- id: 'apm-*',
- name: 'indexpattern-datasource-layer-layer0',
- type: 'index-pattern',
- },
+ { id: 'apm-*', name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern' },
+ { id: 'apm-*', name: 'indexpattern-datasource-layer-layer0', type: 'index-pattern' },
],
state: {
datasourceStates: {
@@ -26,27 +20,25 @@ export const sampleAttributeKpi = {
columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'],
columns: {
'x-axis-column-layer0': {
+ sourceField: '@timestamp',
dataType: 'date',
isBucketed: true,
label: '@timestamp',
operationType: 'date_histogram',
- params: {
- interval: 'auto',
- },
+ params: { interval: 'auto' },
scale: 'interval',
- sourceField: '@timestamp',
},
'y-axis-column-layer0': {
dataType: 'number',
- filter: {
- language: 'kuery',
- query: 'transaction.type: page-load and processor.event: transaction',
- },
isBucketed: false,
- label: 'test-series',
+ label: 'Page views',
operationType: 'count',
scale: 'ratio',
sourceField: 'Records',
+ filter: {
+ query: 'transaction.type: page-load and processor.event: transaction',
+ language: 'kuery',
+ },
},
},
incompleteColumns: {},
@@ -54,51 +46,26 @@ export const sampleAttributeKpi = {
},
},
},
- filters: [],
- query: {
- language: 'kuery',
- query: '',
- },
visualization: {
- axisTitlesVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
- curveType: 'CURVE_MONOTONE_X',
+ legend: { isVisible: true, position: 'right' },
+ valueLabels: 'hide',
fittingFunction: 'Linear',
- gridlinesVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
+ curveType: 'CURVE_MONOTONE_X',
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ preferredSeriesType: 'line',
layers: [
{
accessors: ['y-axis-column-layer0'],
layerId: 'layer0',
seriesType: 'line',
+ yConfig: [{ forAccessor: 'y-axis-column-layer0' }],
xAccessor: 'x-axis-column-layer0',
- yConfig: [
- {
- color: 'green',
- forAccessor: 'y-axis-column-layer0',
- },
- ],
},
],
- legend: {
- isVisible: true,
- position: 'right',
- },
- preferredSeriesType: 'line',
- tickLabelsVisibilitySettings: {
- x: true,
- yLeft: true,
- yRight: true,
- },
- valueLabels: 'hide',
},
+ query: { query: '', language: 'kuery' },
+ filters: [],
},
- title: 'Prefilled from exploratory view app',
- visualizationType: 'lnsXY',
};
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 694250e5749cb..f7df2939d9909 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
@@ -5,7 +5,7 @@
* 2.0.
*/
import rison, { RisonValue } from 'rison-node';
-import type { ReportViewType, SeriesUrl, UrlFilter } from '../types';
+import type { SeriesUrl, UrlFilter } from '../types';
import type { AllSeries, AllShortSeries } from '../hooks/use_series_storage';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
import { esFilters, ExistsFilter } from '../../../../../../../../src/plugins/data/public';
@@ -16,43 +16,40 @@ export function convertToShortUrl(series: SeriesUrl) {
const {
operationType,
seriesType,
+ reportType,
breakdown,
filters,
reportDefinitions,
dataType,
selectedMetricField,
- hidden,
- name,
- color,
...restSeries
} = series;
return {
[URL_KEYS.OPERATION_TYPE]: operationType,
+ [URL_KEYS.REPORT_TYPE]: reportType,
[URL_KEYS.SERIES_TYPE]: seriesType,
[URL_KEYS.BREAK_DOWN]: breakdown,
[URL_KEYS.FILTERS]: filters,
[URL_KEYS.REPORT_DEFINITIONS]: reportDefinitions,
[URL_KEYS.DATA_TYPE]: dataType,
[URL_KEYS.SELECTED_METRIC]: selectedMetricField,
- [URL_KEYS.HIDDEN]: hidden,
- [URL_KEYS.NAME]: name,
- [URL_KEYS.COLOR]: color,
...restSeries,
};
}
-export function createExploratoryViewUrl(
- { reportType, allSeries }: { reportType: ReportViewType; allSeries: AllSeries },
- baseHref = ''
-) {
- const allShortSeries: AllShortSeries = allSeries.map((series) => convertToShortUrl(series));
+export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') {
+ const allSeriesIds = Object.keys(allSeries);
+
+ const allShortSeries: AllShortSeries = {};
+
+ allSeriesIds.forEach((seriesKey) => {
+ allShortSeries[seriesKey] = convertToShortUrl(allSeries[seriesKey]);
+ });
return (
baseHref +
- `/app/observability/exploratory-view/configure#?reportType=${reportType}&sr=${rison.encode(
- (allShortSeries as unknown) as RisonValue
- )}`
+ `/app/observability/exploratory-view#?sr=${rison.encode(allShortSeries as RisonValue)}`
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
index 21c749258bebe..989ebf17c2062 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
@@ -11,13 +11,6 @@ import { render, mockCore, mockAppIndexPattern } from './rtl_helpers';
import { ExploratoryView } from './exploratory_view';
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/test_utils';
import * as obsvInd from './utils/observability_index_patterns';
-import * as pluginHook from '../../../hooks/use_plugin_context';
-
-jest.spyOn(pluginHook, 'usePluginContext').mockReturnValue({
- appMountParameters: {
- setHeaderActionMenu: jest.fn(),
- },
-} as any);
describe('ExploratoryView', () => {
mockAppIndexPattern();
@@ -48,18 +41,29 @@ describe('ExploratoryView', () => {
it('renders exploratory view', async () => {
render();
- expect(await screen.findByText(/Preview/i)).toBeInTheDocument();
- expect(await screen.findByText(/Configure series/i)).toBeInTheDocument();
- expect(await screen.findByText(/Hide chart/i)).toBeInTheDocument();
- expect(await screen.findByText(/Refresh/i)).toBeInTheDocument();
+ expect(await screen.findByText(/open in lens/i)).toBeInTheDocument();
expect(
await screen.findByRole('heading', { name: /Performance Distribution/i })
).toBeInTheDocument();
});
it('renders lens component when there is series', async () => {
- render();
+ const initSeries = {
+ data: {
+ 'ux-series': {
+ isNew: true,
+ dataType: 'ux' as const,
+ reportType: 'data-distribution' as const,
+ breakdown: 'user_agent .name',
+ reportDefinitions: { 'service.name': ['elastic-co'] },
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ };
+
+ render(, { initSeries });
+ expect(await screen.findByText(/open in lens/i)).toBeInTheDocument();
expect((await screen.findAllByText('Performance distribution'))[0]).toBeInTheDocument();
expect(await screen.findByText(/Lens Embeddable Component/i)).toBeInTheDocument();
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 cb901b8b588f3..af04108c56790 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
@@ -4,13 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import { i18n } from '@kbn/i18n';
import React, { useEffect, useRef, useState } from 'react';
-import { EuiButtonEmpty, EuiPanel, EuiResizableContainer, EuiTitle } from '@elastic/eui';
+import { EuiPanel, EuiTitle } from '@elastic/eui';
import styled from 'styled-components';
-import { useRouteMatch } from 'react-router-dom';
-import { PanelDirection } from '@elastic/eui/src/components/resizable_container/types';
+import { isEmpty } from 'lodash';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { ExploratoryViewHeader } from './header/header';
@@ -18,15 +16,40 @@ import { useSeriesStorage } from './hooks/use_series_storage';
import { useLensAttributes } from './hooks/use_lens_attributes';
import { TypedLensByValueInput } from '../../../../../lens/public';
import { useAppIndexPatternContext } from './hooks/use_app_index_pattern';
-import { SeriesViews } from './views/series_views';
+import { SeriesBuilder } from './series_builder/series_builder';
+import { SeriesUrl } from './types';
import { LensEmbeddable } from './lens_embeddable';
import { EmptyView } from './components/empty_view';
-export type PanelId = 'seriesPanel' | 'chartPanel';
+export const combineTimeRanges = (
+ allSeries: Record,
+ firstSeries?: SeriesUrl
+) => {
+ let to: string = '';
+ let from: string = '';
+ if (firstSeries?.reportType === 'kpi-over-time') {
+ return firstSeries.time;
+ }
+ Object.values(allSeries ?? {}).forEach((series) => {
+ if (series.dataType && series.reportType && !isEmpty(series.reportDefinitions)) {
+ const seriesTo = new Date(series.time.to);
+ const seriesFrom = new Date(series.time.from);
+ if (!to || seriesTo > new Date(to)) {
+ to = series.time.to;
+ }
+ if (!from || seriesFrom < new Date(from)) {
+ from = series.time.from;
+ }
+ }
+ });
+ return { to, from };
+};
export function ExploratoryView({
saveAttributes,
+ multiSeries,
}: {
+ multiSeries?: boolean;
saveAttributes?: (attr: TypedLensByValueInput['attributes'] | null) => void;
}) {
const {
@@ -46,19 +69,20 @@ export function ExploratoryView({
const { loadIndexPattern, loading } = useAppIndexPatternContext();
- const { firstSeries, allSeries, lastRefresh, reportType } = useSeriesStorage();
+ const { firstSeries, firstSeriesId, allSeries } = useSeriesStorage();
const lensAttributesT = useLensAttributes();
const setHeightOffset = () => {
if (seriesBuilderRef?.current && wrapperRef.current) {
const headerOffset = wrapperRef.current.getBoundingClientRect().top;
- setHeight(`calc(100vh - ${headerOffset + 40}px)`);
+ const seriesOffset = seriesBuilderRef.current.getBoundingClientRect().height;
+ setHeight(`calc(100vh - ${seriesOffset + headerOffset + 40}px)`);
}
};
useEffect(() => {
- allSeries.forEach((seriesT) => {
+ Object.values(allSeries).forEach((seriesT) => {
loadIndexPattern({
dataType: seriesT.dataType,
});
@@ -72,104 +96,38 @@ export function ExploratoryView({
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [JSON.stringify(lensAttributesT ?? {}), lastRefresh]);
+ }, [JSON.stringify(lensAttributesT ?? {})]);
useEffect(() => {
setHeightOffset();
});
- const collapseFn = useRef<(id: PanelId, direction: PanelDirection) => void>();
-
- const [hiddenPanel, setHiddenPanel] = useState('');
-
- const isPreview = !!useRouteMatch('/exploratory-view/preview');
-
- const onCollapse = (panelId: string) => {
- setHiddenPanel((prevState) => (panelId === prevState ? '' : panelId));
- };
-
- const onChange = (panelId: PanelId) => {
- onCollapse(panelId);
- if (collapseFn.current) {
- collapseFn.current(panelId, panelId === 'seriesPanel' ? 'right' : 'left');
- }
- };
-
return (
{lens ? (
<>
-
+
-
- {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
- collapseFn.current = (id, direction) => togglePanel?.(id, { direction });
-
- return (
- <>
-
- {lensAttributes ? (
-
- ) : (
-
- )}
-
-
-
- {!isPreview &&
- (hiddenPanel === 'chartPanel' ? (
- onChange('chartPanel')} iconType="arrowDown">
- {SHOW_CHART_LABEL}
-
- ) : (
- onChange('chartPanel')}
- iconType="arrowUp"
- color="text"
- >
- {HIDE_CHART_LABEL}
-
- ))}
-
-
- >
- );
- }}
-
- {hiddenPanel === 'seriesPanel' && (
- onChange('seriesPanel')} iconType="arrowUp">
- {PREVIEW_LABEL}
-
+ {lensAttributes ? (
+
+ ) : (
+
)}
+
>
) : (
- {LENS_NOT_AVAILABLE}
+
+ {i18n.translate('xpack.observability.overview.exploratoryView.lensDisabled', {
+ defaultMessage:
+ 'Lens app is not available, please enable Lens to use exploratory view.',
+ })}
+
)}
@@ -189,39 +147,4 @@ const Wrapper = styled(EuiPanel)`
margin: 0 auto;
width: 100%;
overflow-x: auto;
- position: relative;
-`;
-
-const ShowPreview = styled(EuiButtonEmpty)`
- position: absolute;
- bottom: 34px;
-`;
-const HideChart = styled(EuiButtonEmpty)`
- position: absolute;
- top: -35px;
- right: 50px;
`;
-const ShowChart = styled(EuiButtonEmpty)`
- position: absolute;
- top: -10px;
- right: 50px;
-`;
-
-const HIDE_CHART_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.hideChart', {
- defaultMessage: 'Hide chart',
-});
-
-const SHOW_CHART_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.showChart', {
- defaultMessage: 'Show chart',
-});
-
-const PREVIEW_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.preview', {
- defaultMessage: 'Preview',
-});
-
-const LENS_NOT_AVAILABLE = i18n.translate(
- 'xpack.observability.overview.exploratoryView.lensDisabled',
- {
- defaultMessage: 'Lens app is not available, please enable Lens to use exploratory view.',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
index 1f910b946deb3..8cd8977fcf741 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
@@ -8,22 +8,51 @@
import React from 'react';
import { render } from '../rtl_helpers';
import { ExploratoryViewHeader } from './header';
-import * as pluginHook from '../../../../hooks/use_plugin_context';
-
-jest.spyOn(pluginHook, 'usePluginContext').mockReturnValue({
- appMountParameters: {
- setHeaderActionMenu: jest.fn(),
- },
-} as any);
+import { fireEvent } from '@testing-library/dom';
describe('ExploratoryViewHeader', function () {
it('should render properly', function () {
const { getByText } = render(
);
- getByText('Refresh');
+ getByText('Open in Lens');
+ });
+
+ it('should be able to click open in lens', function () {
+ const initSeries = {
+ data: {
+ 'uptime-pings-histogram': {
+ dataType: 'synthetics' as const,
+ reportType: 'kpi-over-time' as const,
+ breakdown: 'monitor.status',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ };
+
+ const { getByText, core } = render(
+ ,
+ { initSeries }
+ );
+ fireEvent.click(getByText('Open in Lens'));
+
+ expect(core?.lens?.navigateToPrefilledEditor).toHaveBeenCalledTimes(1);
+ expect(core?.lens?.navigateToPrefilledEditor).toHaveBeenCalledWith(
+ {
+ attributes: { title: 'Performance distribution' },
+ id: '',
+ timeRange: {
+ from: 'now-15m',
+ to: 'now',
+ },
+ },
+ true
+ );
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
index bec8673f88b4e..ded56ec9e817f 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
@@ -5,37 +5,43 @@
* 2.0.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBetaBadge, EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
-import { TypedLensByValueInput } from '../../../../../../lens/public';
+import { TypedLensByValueInput, LensEmbeddableInput } from '../../../../../../lens/public';
+import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { DataViewLabels } from '../configurations/constants';
+import { ObservabilityAppServices } from '../../../../application/types';
import { useSeriesStorage } from '../hooks/use_series_storage';
-import { LastUpdated } from './last_updated';
-import { combineTimeRanges } from '../lens_embeddable';
-import { ExpViewActionMenu } from '../components/action_menu';
+import { combineTimeRanges } from '../exploratory_view';
interface Props {
- seriesId?: number;
- lastUpdated?: number;
+ seriesId: string;
lensAttributes: TypedLensByValueInput['attributes'] | null;
}
-export function ExploratoryViewHeader({ seriesId, lensAttributes, lastUpdated }: Props) {
- const { getSeries, allSeries, setLastRefresh, reportType } = useSeriesStorage();
+export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) {
+ const kServices = useKibana().services;
- const series = seriesId ? getSeries(seriesId) : undefined;
+ const { lens } = kServices;
- const timeRange = combineTimeRanges(reportType, allSeries, series);
+ const { getSeries, allSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
+ const [isSaveOpen, setIsSaveOpen] = useState(false);
+
+ const LensSaveModalComponent = lens.SaveModalComponent;
+
+ const timeRange = combineTimeRanges(allSeries, series);
return (
<>
-
- {DataViewLabels[reportType] ??
+ {DataViewLabels[series.reportType] ??
i18n.translate('xpack.observability.expView.heading.label', {
defaultMessage: 'Analyze data',
})}{' '}
@@ -51,18 +57,53 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes, lastUpdated }:
-
+ {
+ if (lensAttributes) {
+ lens.navigateToPrefilledEditor(
+ {
+ id: '',
+ timeRange,
+ attributes: lensAttributes,
+ },
+ true
+ );
+ }
+ }}
+ >
+ {i18n.translate('xpack.observability.expView.heading.openInLens', {
+ defaultMessage: 'Open in Lens',
+ })}
+
- setLastRefresh(Date.now())}>
- {REFRESH_LABEL}
+ {
+ if (lensAttributes) {
+ setIsSaveOpen(true);
+ }
+ }}
+ >
+ {i18n.translate('xpack.observability.expView.heading.saveLensVisualization', {
+ defaultMessage: 'Save',
+ })}
+
+ {isSaveOpen && lensAttributes && (
+ setIsSaveOpen(false)}
+ onSave={() => {}}
+ />
+ )}
>
);
}
-
-const REFRESH_LABEL = i18n.translate('xpack.observability.overview.exploratoryView.refresh', {
- defaultMessage: 'Refresh',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx
index d65917093d129..7a5f12a72b1f0 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx
@@ -27,7 +27,7 @@ interface ProviderProps {
}
type HasAppDataState = Record;
-export type IndexPatternState = Record;
+type IndexPatternState = Record;
type LoadingState = Record;
export function IndexPatternContextProvider({ children }: ProviderProps) {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx
deleted file mode 100644
index e86144c124949..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx
+++ /dev/null
@@ -1,92 +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 { useCallback, useEffect, useState } from 'react';
-import { useKibana } from '../../../../utils/kibana_react';
-import { SeriesConfig, SeriesUrl } from '../types';
-import { useAppIndexPatternContext } from './use_app_index_pattern';
-import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter } from '../configurations/utils';
-import { getFiltersFromDefs } from './use_lens_attributes';
-import { RECORDS_FIELD, RECORDS_PERCENTAGE_FIELD } from '../configurations/constants';
-
-interface UseDiscoverLink {
- seriesConfig: SeriesConfig;
- series: SeriesUrl;
-}
-
-export const useDiscoverLink = ({ series, seriesConfig }: UseDiscoverLink) => {
- const kServices = useKibana().services;
- const {
- application: { navigateToUrl },
- } = kServices;
-
- const { indexPatterns } = useAppIndexPatternContext();
-
- const urlGenerator = kServices.discover?.urlGenerator;
- const [discoverUrl, setDiscoverUrl] = useState('');
-
- useEffect(() => {
- const indexPattern = indexPatterns?.[series.dataType];
-
- const definitions = series.reportDefinitions ?? {};
- const filters = [...(seriesConfig?.baseFilters ?? [])];
-
- const definitionFilters = getFiltersFromDefs(definitions);
-
- definitionFilters.forEach(({ field, values = [] }) => {
- if (values.length > 1) {
- filters.push(buildPhrasesFilter(field, values, indexPattern)[0]);
- } else {
- filters.push(buildPhraseFilter(field, values[0], indexPattern)[0]);
- }
- });
-
- const selectedMetricField = series.selectedMetricField;
-
- if (
- selectedMetricField &&
- selectedMetricField !== RECORDS_FIELD &&
- selectedMetricField !== RECORDS_PERCENTAGE_FIELD
- ) {
- filters.push(buildExistsFilter(selectedMetricField, indexPattern)[0]);
- }
-
- const getDiscoverUrl = async () => {
- if (!urlGenerator?.createUrl) return;
-
- const newUrl = await urlGenerator.createUrl({
- filters,
- indexPatternId: indexPattern?.id,
- });
- setDiscoverUrl(newUrl);
- };
- getDiscoverUrl();
- }, [
- indexPatterns,
- series.dataType,
- series.reportDefinitions,
- series.selectedMetricField,
- seriesConfig?.baseFilters,
- urlGenerator,
- ]);
-
- const onClick = useCallback(
- (event: React.MouseEvent) => {
- if (discoverUrl) {
- event.preventDefault();
-
- return navigateToUrl(discoverUrl);
- }
- },
- [discoverUrl, navigateToUrl]
- );
-
- return {
- href: discoverUrl,
- onClick,
- };
-};
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 71945734eeabc..8bb265b4f6d89 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
@@ -9,18 +9,12 @@ import { useMemo } from 'react';
import { isEmpty } from 'lodash';
import { TypedLensByValueInput } from '../../../../../../lens/public';
import { LayerConfig, LensAttributes } from '../configurations/lens_attributes';
-import {
- AllSeries,
- allSeriesKey,
- convertAllShortSeries,
- useSeriesStorage,
-} from './use_series_storage';
+import { useSeriesStorage } from './use_series_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
import { SeriesUrl, UrlFilter } from '../types';
import { useAppIndexPatternContext } from './use_app_index_pattern';
import { ALL_VALUES_SELECTED } from '../../field_value_suggestions/field_value_combobox';
-import { useTheme } from '../../../../hooks/use_theme';
export const getFiltersFromDefs = (reportDefinitions: SeriesUrl['reportDefinitions']) => {
return Object.entries(reportDefinitions ?? {})
@@ -34,56 +28,41 @@ export const getFiltersFromDefs = (reportDefinitions: SeriesUrl['reportDefinitio
};
export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null => {
- const { storage, autoApply, allSeries, lastRefresh, reportType } = useSeriesStorage();
+ const { allSeriesIds, allSeries } = useSeriesStorage();
const { indexPatterns } = useAppIndexPatternContext();
- const theme = useTheme();
-
return useMemo(() => {
- if (isEmpty(indexPatterns) || isEmpty(allSeries) || !reportType) {
+ if (isEmpty(indexPatterns) || isEmpty(allSeriesIds)) {
return null;
}
- const allSeriesT: AllSeries = autoApply
- ? allSeries
- : convertAllShortSeries(storage.get(allSeriesKey) ?? []);
-
const layerConfigs: LayerConfig[] = [];
- allSeriesT.forEach((series, seriesIndex) => {
- const indexPattern = indexPatterns?.[series?.dataType];
-
- if (
- indexPattern &&
- !isEmpty(series.reportDefinitions) &&
- !series.hidden &&
- series.selectedMetricField
- ) {
+ allSeriesIds.forEach((seriesIdT) => {
+ const seriesT = allSeries[seriesIdT];
+ const indexPattern = indexPatterns?.[seriesT?.dataType];
+ if (indexPattern && seriesT.reportType && !isEmpty(seriesT.reportDefinitions)) {
const seriesConfig = getDefaultConfigs({
- reportType,
+ reportType: seriesT.reportType,
+ dataType: seriesT.dataType,
indexPattern,
- dataType: series.dataType,
});
- const filters: UrlFilter[] = (series.filters ?? []).concat(
- getFiltersFromDefs(series.reportDefinitions)
+ const filters: UrlFilter[] = (seriesT.filters ?? []).concat(
+ getFiltersFromDefs(seriesT.reportDefinitions)
);
- const color = `euiColorVis${seriesIndex}`;
-
layerConfigs.push({
filters,
indexPattern,
seriesConfig,
- time: series.time,
- name: series.name,
- breakdown: series.breakdown,
- seriesType: series.seriesType,
- operationType: series.operationType,
- reportDefinitions: series.reportDefinitions ?? {},
- selectedMetricField: series.selectedMetricField,
- color: series.color ?? ((theme.eui as unknown) as Record)[color],
+ time: seriesT.time,
+ breakdown: seriesT.breakdown,
+ seriesType: seriesT.seriesType,
+ operationType: seriesT.operationType,
+ reportDefinitions: seriesT.reportDefinitions ?? {},
+ selectedMetricField: seriesT.selectedMetricField,
});
}
});
@@ -94,6 +73,6 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null
const lensAttributes = new LensAttributes(layerConfigs);
- return lensAttributes.getJSON(lastRefresh);
- }, [indexPatterns, allSeries, reportType, autoApply, storage, theme, lastRefresh]);
+ return lensAttributes.getJSON();
+ }, [indexPatterns, allSeriesIds, allSeries]);
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts
index f2a6130cdc59d..2d2618bc46152 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_filters.ts
@@ -6,16 +6,18 @@
*/
import { useSeriesStorage } from './use_series_storage';
-import { SeriesUrl, UrlFilter } from '../types';
+import { UrlFilter } from '../types';
export interface UpdateFilter {
field: string;
- value: string | string[];
+ value: string;
negate?: boolean;
}
-export const useSeriesFilters = ({ seriesId, series }: { seriesId: number; series: SeriesUrl }) => {
- const { setSeries } = useSeriesStorage();
+export const useSeriesFilters = ({ seriesId }: { seriesId: string }) => {
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
const filters = series.filters ?? [];
@@ -24,14 +26,10 @@ export const useSeriesFilters = ({ seriesId, series }: { seriesId: number; serie
.map((filter) => {
if (filter.field === field) {
if (negate) {
- const notValuesN = filter.notValues?.filter((val) =>
- value instanceof Array ? !value.includes(val) : val !== value
- );
+ const notValuesN = filter.notValues?.filter((val) => val !== value);
return { ...filter, notValues: notValuesN };
} else {
- const valuesN = filter.values?.filter((val) =>
- value instanceof Array ? !value.includes(val) : val !== value
- );
+ const valuesN = filter.values?.filter((val) => val !== value);
return { ...filter, values: valuesN };
}
}
@@ -45,9 +43,9 @@ export const useSeriesFilters = ({ seriesId, series }: { seriesId: number; serie
const addFilter = ({ field, value, negate }: UpdateFilter) => {
const currFilter: UrlFilter = { field };
if (negate) {
- currFilter.notValues = value instanceof Array ? value : [value];
+ currFilter.notValues = [value];
} else {
- currFilter.values = value instanceof Array ? value : [value];
+ currFilter.values = [value];
}
if (filters.length === 0) {
setSeries(seriesId, { ...series, filters: [currFilter] });
@@ -67,26 +65,13 @@ export const useSeriesFilters = ({ seriesId, series }: { seriesId: number; serie
const currNotValues = currFilter.notValues ?? [];
const currValues = currFilter.values ?? [];
- const notValues = currNotValues.filter((val) =>
- value instanceof Array ? !value.includes(val) : val !== value
- );
-
- const values = currValues.filter((val) =>
- value instanceof Array ? !value.includes(val) : val !== value
- );
+ const notValues = currNotValues.filter((val) => val !== value);
+ const values = currValues.filter((val) => val !== value);
if (negate) {
- if (value instanceof Array) {
- notValues.push(...value);
- } else {
- notValues.push(value);
- }
+ notValues.push(value);
} else {
- if (value instanceof Array) {
- values.push(...value);
- } else {
- values.push(value);
- }
+ values.push(value);
}
currFilter.notValues = notValues.length > 0 ? notValues : undefined;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx
index ce6d7bd94d8e4..c32acc47abd1b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx
@@ -6,39 +6,37 @@
*/
import React, { useEffect } from 'react';
-import { Route, Router } from 'react-router-dom';
-import { render } from '@testing-library/react';
+
import { UrlStorageContextProvider, useSeriesStorage } from './use_series_storage';
-import { getHistoryFromUrl } from '../rtl_helpers';
+import { render } from '@testing-library/react';
-const mockSingleSeries = [
- {
- name: 'performance-distribution',
+const mockSingleSeries = {
+ 'performance-distribution': {
+ reportType: 'data-distribution',
dataType: 'ux',
breakdown: 'user_agent.name',
time: { from: 'now-15m', to: 'now' },
},
-];
+};
-const mockMultipleSeries = [
- {
- name: 'performance-distribution',
+const mockMultipleSeries = {
+ 'performance-distribution': {
+ reportType: 'data-distribution',
dataType: 'ux',
breakdown: 'user_agent.name',
time: { from: 'now-15m', to: 'now' },
},
- {
- name: 'kpi-over-time',
+ 'kpi-over-time': {
+ reportType: 'kpi-over-time',
dataType: 'synthetics',
breakdown: 'user_agent.name',
time: { from: 'now-15m', to: 'now' },
},
-];
+};
-describe('userSeriesStorage', function () {
+describe('userSeries', function () {
function setupTestComponent(seriesData: any) {
const setData = jest.fn();
-
function TestComponent() {
const data = useSeriesStorage();
@@ -50,20 +48,11 @@ describe('userSeriesStorage', function () {
}
render(
-
-
- (key === 'sr' ? seriesData : null)),
- set: jest.fn(),
- }}
- >
-
-
-
-
+
+
+
);
return setData;
@@ -74,20 +63,22 @@ describe('userSeriesStorage', function () {
expect(setData).toHaveBeenCalledTimes(2);
expect(setData).toHaveBeenLastCalledWith(
expect.objectContaining({
- allSeries: [
- {
- name: 'performance-distribution',
- dataType: 'ux',
+ allSeries: {
+ 'performance-distribution': {
breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
time: { from: 'now-15m', to: 'now' },
},
- ],
+ },
+ allSeriesIds: ['performance-distribution'],
firstSeries: {
- name: 'performance-distribution',
- dataType: 'ux',
breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
time: { from: 'now-15m', to: 'now' },
},
+ firstSeriesId: 'performance-distribution',
})
);
});
@@ -98,38 +89,42 @@ describe('userSeriesStorage', function () {
expect(setData).toHaveBeenCalledTimes(2);
expect(setData).toHaveBeenLastCalledWith(
expect.objectContaining({
- allSeries: [
- {
- name: 'performance-distribution',
- dataType: 'ux',
+ allSeries: {
+ 'performance-distribution': {
breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
time: { from: 'now-15m', to: 'now' },
},
- {
- name: 'kpi-over-time',
+ 'kpi-over-time': {
+ reportType: 'kpi-over-time',
dataType: 'synthetics',
breakdown: 'user_agent.name',
time: { from: 'now-15m', to: 'now' },
},
- ],
+ },
+ allSeriesIds: ['performance-distribution', 'kpi-over-time'],
firstSeries: {
- name: 'performance-distribution',
- dataType: 'ux',
breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
time: { from: 'now-15m', to: 'now' },
},
+ firstSeriesId: 'performance-distribution',
})
);
});
it('should return expected result when there are no series', function () {
- const setData = setupTestComponent([]);
+ const setData = setupTestComponent({});
- expect(setData).toHaveBeenCalledTimes(1);
+ expect(setData).toHaveBeenCalledTimes(2);
expect(setData).toHaveBeenLastCalledWith(
expect.objectContaining({
- allSeries: [],
+ allSeries: {},
+ allSeriesIds: [],
firstSeries: undefined,
+ firstSeriesId: undefined,
})
);
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
index 04f8751e2a0b6..a47a124d14b4d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
@@ -6,7 +6,6 @@
*/
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
-import { useRouteMatch } from 'react-router-dom';
import {
IKbnUrlStateStorage,
ISessionStorageStateStorage,
@@ -23,19 +22,13 @@ import { OperationType, SeriesType } from '../../../../../../lens/public';
import { URL_KEYS } from '../configurations/constants/url_constants';
export interface SeriesContextValue {
- firstSeries?: SeriesUrl;
- autoApply: boolean;
- lastRefresh: number;
- setLastRefresh: (val: number) => void;
- setAutoApply: (val: boolean) => void;
- applyChanges: () => void;
+ firstSeries: SeriesUrl;
+ firstSeriesId: string;
+ allSeriesIds: string[];
allSeries: AllSeries;
- setSeries: (seriesIndex: number, newValue: SeriesUrl) => void;
- getSeries: (seriesIndex: number) => SeriesUrl | undefined;
- removeSeries: (seriesIndex: number) => void;
- setReportType: (reportType: string) => void;
- storage: IKbnUrlStateStorage | ISessionStorageStateStorage;
- reportType: ReportViewType;
+ setSeries: (seriesIdN: string, newValue: SeriesUrl) => void;
+ getSeries: (seriesId: string) => SeriesUrl;
+ removeSeries: (seriesId: string) => void;
}
export const UrlStorageContext = createContext({} as SeriesContextValue);
@@ -43,112 +36,72 @@ interface ProviderProps {
storage: IKbnUrlStateStorage | ISessionStorageStateStorage;
}
-export function convertAllShortSeries(allShortSeries: AllShortSeries) {
- return (allShortSeries ?? []).map((shortSeries) => convertFromShortUrl(shortSeries));
-}
+function convertAllShortSeries(allShortSeries: AllShortSeries) {
+ const allSeriesIds = Object.keys(allShortSeries);
+ const allSeriesN: AllSeries = {};
+ allSeriesIds.forEach((seriesKey) => {
+ allSeriesN[seriesKey] = convertFromShortUrl(allShortSeries[seriesKey]);
+ });
-export const allSeriesKey = 'sr';
-const autoApplyKey = 'autoApply';
-const reportTypeKey = 'reportType';
+ return allSeriesN;
+}
export function UrlStorageContextProvider({
children,
storage,
}: ProviderProps & { children: JSX.Element }) {
- const [allSeries, setAllSeries] = useState(() =>
- convertAllShortSeries(storage.get(allSeriesKey) ?? [])
- );
-
- const [autoApply, setAutoApply] = useState(() => storage.get(autoApplyKey) ?? true);
- const [lastRefresh, setLastRefresh] = useState(() => Date.now());
+ const allSeriesKey = 'sr';
- const [reportType, setReportType] = useState(
- () => (storage as IKbnUrlStateStorage).get(reportTypeKey) ?? ''
+ const [allShortSeries, setAllShortSeries] = useState(
+ () => storage.get(allSeriesKey) ?? {}
);
-
+ const [allSeries, setAllSeries] = useState(() =>
+ convertAllShortSeries(storage.get(allSeriesKey) ?? {})
+ );
+ const [firstSeriesId, setFirstSeriesId] = useState('');
const [firstSeries, setFirstSeries] = useState();
- const isPreview = !!useRouteMatch('/exploratory-view/preview');
-
- useEffect(() => {
- const allShortSeries = allSeries.map((series) => convertToShortUrl(series));
-
- const firstSeriesT = allSeries?.[0];
-
- setFirstSeries(firstSeriesT);
-
- if (autoApply) {
- (storage as IKbnUrlStateStorage).set(allSeriesKey, allShortSeries);
- }
- }, [allSeries, autoApply, storage]);
useEffect(() => {
- // needed for tab change
- const allShortSeries = allSeries.map((series) => convertToShortUrl(series));
+ const allSeriesIds = Object.keys(allShortSeries);
+ const allSeriesN: AllSeries = convertAllShortSeries(allShortSeries ?? {});
+ setAllSeries(allSeriesN);
+ setFirstSeriesId(allSeriesIds?.[0]);
+ setFirstSeries(allSeriesN?.[allSeriesIds?.[0]]);
(storage as IKbnUrlStateStorage).set(allSeriesKey, allShortSeries);
- (storage as IKbnUrlStateStorage).set(reportTypeKey, reportType);
- // this is only needed for tab change, so we will not add allSeries into dependencies
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isPreview, storage]);
-
- const setSeries = useCallback((seriesIndex: number, newValue: SeriesUrl) => {
- setAllSeries((prevAllSeries) => {
- const newStateRest = prevAllSeries.map((series, index) => {
- if (index === seriesIndex) {
- return newValue;
- }
- return series;
- });
-
- if (prevAllSeries.length === seriesIndex) {
- return [...newStateRest, newValue];
- }
-
- return [...newStateRest];
+ }, [allShortSeries, storage]);
+
+ const setSeries = (seriesIdN: string, newValue: SeriesUrl) => {
+ setAllShortSeries((prevState) => {
+ prevState[seriesIdN] = convertToShortUrl(newValue);
+ return { ...prevState };
});
- }, []);
+ };
- useEffect(() => {
- (storage as IKbnUrlStateStorage).set(reportTypeKey, reportType);
- }, [reportType, storage]);
+ const removeSeries = (seriesIdN: string) => {
+ setAllShortSeries((prevState) => {
+ delete prevState[seriesIdN];
+ return { ...prevState };
+ });
+ };
- const removeSeries = useCallback((seriesIndex: number) => {
- setAllSeries((prevAllSeries) =>
- prevAllSeries.filter((seriesT, index) => index !== seriesIndex)
- );
- }, []);
+ const allSeriesIds = Object.keys(allShortSeries);
const getSeries = useCallback(
- (seriesIndex: number) => {
- return allSeries[seriesIndex];
+ (seriesId?: string) => {
+ return seriesId ? allSeries?.[seriesId] ?? {} : ({} as SeriesUrl);
},
[allSeries]
);
- const applyChanges = useCallback(() => {
- const allShortSeries = allSeries.map((series) => convertToShortUrl(series));
-
- (storage as IKbnUrlStateStorage).set(allSeriesKey, allShortSeries);
- setLastRefresh(Date.now());
- }, [allSeries, storage]);
-
- useEffect(() => {
- (storage as IKbnUrlStateStorage).set(autoApplyKey, autoApply);
- }, [autoApply, storage]);
-
const value = {
- autoApply,
- setAutoApply,
- applyChanges,
storage,
getSeries,
setSeries,
removeSeries,
+ firstSeriesId,
allSeries,
- lastRefresh,
- setLastRefresh,
- setReportType,
- reportType: storage.get(reportTypeKey) as ReportViewType,
+ allSeriesIds,
firstSeries: firstSeries!,
};
return {children};
@@ -159,9 +112,10 @@ export function useSeriesStorage() {
}
function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl {
- const { dt, op, st, bd, ft, time, rdf, mt, h, n, c, ...restSeries } = newValue;
+ const { dt, op, st, rt, bd, ft, time, rdf, mt, ...restSeries } = newValue;
return {
operationType: op,
+ reportType: rt!,
seriesType: st,
breakdown: bd,
filters: ft!,
@@ -169,31 +123,26 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl {
reportDefinitions: rdf,
dataType: dt!,
selectedMetricField: mt,
- hidden: h,
- name: n,
- color: c,
...restSeries,
};
}
interface ShortUrlSeries {
[URL_KEYS.OPERATION_TYPE]?: OperationType;
+ [URL_KEYS.REPORT_TYPE]?: ReportViewType;
[URL_KEYS.DATA_TYPE]?: AppDataType;
[URL_KEYS.SERIES_TYPE]?: SeriesType;
[URL_KEYS.BREAK_DOWN]?: string;
[URL_KEYS.FILTERS]?: UrlFilter[];
[URL_KEYS.REPORT_DEFINITIONS]?: URLReportDefinition;
[URL_KEYS.SELECTED_METRIC]?: string;
- [URL_KEYS.HIDDEN]?: boolean;
- [URL_KEYS.NAME]: string;
- [URL_KEYS.COLOR]?: string;
time?: {
to: string;
from: string;
};
}
-export type AllShortSeries = ShortUrlSeries[];
-export type AllSeries = SeriesUrl[];
+export type AllShortSeries = Record;
+export type AllSeries = Record;
-export const NEW_SERIES_KEY = 'new-series';
+export const NEW_SERIES_KEY = 'new-series-key';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx
index 3de29b02853e8..e55752ceb62ba 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx
@@ -25,9 +25,11 @@ import { TypedLensByValueInput } from '../../../../../lens/public';
export function ExploratoryViewPage({
saveAttributes,
+ multiSeries = false,
useSessionStorage = false,
}: {
useSessionStorage?: boolean;
+ multiSeries?: boolean;
saveAttributes?: (attr: TypedLensByValueInput['attributes'] | null) => void;
}) {
useTrackPageview({ app: 'observability-overview', path: 'exploratory-view' });
@@ -59,7 +61,7 @@ export function ExploratoryViewPage({
-
+
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
index 9e4d9486dc155..4cb586fe94ceb 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
@@ -7,51 +7,16 @@
import { i18n } from '@kbn/i18n';
import React, { Dispatch, SetStateAction, useCallback } from 'react';
-import styled from 'styled-components';
-import { isEmpty } from 'lodash';
+import { combineTimeRanges } from './exploratory_view';
import { TypedLensByValueInput } from '../../../../../lens/public';
import { useSeriesStorage } from './hooks/use_series_storage';
import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
-import { ReportViewType, SeriesUrl } from './types';
-import { ReportTypes } from './configurations/constants';
interface Props {
lensAttributes: TypedLensByValueInput['attributes'];
setLastUpdated: Dispatch>;
}
-export const combineTimeRanges = (
- reportType: ReportViewType,
- allSeries: SeriesUrl[],
- firstSeries?: SeriesUrl
-) => {
- let to: string = '';
- let from: string = '';
-
- if (reportType === ReportTypes.KPI) {
- return firstSeries?.time;
- }
-
- allSeries.forEach((series) => {
- if (
- series.dataType &&
- series.selectedMetricField &&
- !isEmpty(series.reportDefinitions) &&
- series.time
- ) {
- const seriesTo = new Date(series.time.to);
- const seriesFrom = new Date(series.time.from);
- if (!to || seriesTo > new Date(to)) {
- to = series.time.to;
- }
- if (!from || seriesFrom < new Date(from)) {
- from = series.time.from;
- }
- }
- });
-
- return { to, from };
-};
export function LensEmbeddable(props: Props) {
const { lensAttributes, setLastUpdated } = props;
@@ -62,11 +27,9 @@ export function LensEmbeddable(props: Props) {
const LensComponent = lens?.EmbeddableComponent;
- const { firstSeries, setSeries, allSeries, reportType } = useSeriesStorage();
+ const { firstSeriesId, firstSeries: series, setSeries, allSeries } = useSeriesStorage();
- const firstSeriesId = 0;
-
- const timeRange = firstSeries ? combineTimeRanges(reportType, allSeries, firstSeries) : null;
+ const timeRange = combineTimeRanges(allSeries, series);
const onLensLoad = useCallback(() => {
setLastUpdated(Date.now());
@@ -74,9 +37,9 @@ export function LensEmbeddable(props: Props) {
const onBrushEnd = useCallback(
({ range }: { range: number[] }) => {
- if (reportType !== 'data-distribution' && firstSeries) {
+ if (series?.reportType !== 'data-distribution') {
setSeries(firstSeriesId, {
- ...firstSeries,
+ ...series,
time: {
from: new Date(range[0]).toISOString(),
to: new Date(range[1]).toISOString(),
@@ -90,30 +53,16 @@ export function LensEmbeddable(props: Props) {
);
}
},
- [reportType, setSeries, firstSeries, notifications?.toasts]
+ [notifications?.toasts, series, firstSeriesId, setSeries]
);
- if (timeRange === null || !firstSeries) {
- return null;
- }
-
return (
-
-
-
+
);
}
-
-const LensWrapper = styled.div`
- height: 100%;
-
- &&& > div {
- height: 100%;
- }
-`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
index 0e609cbe6c9e5..972e3beb4b722 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
@@ -10,7 +10,7 @@ import React, { ReactElement } from 'react';
import { stringify } from 'query-string';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as reactTestLibRender, RenderOptions } from '@testing-library/react';
-import { Route, Router } from 'react-router-dom';
+import { Router } from 'react-router-dom';
import { createMemoryHistory, History } from 'history';
import { CoreStart } from 'kibana/public';
import { I18nProvider } from '@kbn/i18n/react';
@@ -24,7 +24,7 @@ import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/
import { lensPluginMock } from '../../../../../lens/public/mocks';
import * as useAppIndexPatternHook from './hooks/use_app_index_pattern';
import { IndexPatternContextProvider } from './hooks/use_app_index_pattern';
-import { AllSeries, SeriesContextValue, UrlStorageContext } from './hooks/use_series_storage';
+import { AllSeries, UrlStorageContext } from './hooks/use_series_storage';
import * as fetcherHook from '../../../hooks/use_fetcher';
import * as useSeriesFilterHook from './hooks/use_series_filters';
@@ -39,10 +39,9 @@ import {
IndexPattern,
IndexPatternsContract,
} from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
-import { AppDataType, SeriesUrl, UrlFilter } from './types';
+import { AppDataType, UrlFilter } from './types';
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
import { ListItem } from '../../../hooks/use_values_list';
-import { TRANSACTION_DURATION } from './configurations/constants/elasticsearch_fieldnames';
interface KibanaProps {
services?: KibanaServices;
@@ -159,11 +158,9 @@ export function MockRouter({
}: MockRouterProps) {
return (
-
-
- {children}
-
-
+
+ {children}
+
);
}
@@ -176,7 +173,7 @@ export function render(
core: customCore,
kibanaProps,
renderOptions,
- url = '/app/observability/exploratory-view/configure#?autoApply=!t',
+ url,
initSeries = {},
}: RenderRouterOptions = {}
) {
@@ -206,7 +203,7 @@ export function render(
};
}
-export const getHistoryFromUrl = (url: Url) => {
+const getHistoryFromUrl = (url: Url) => {
if (typeof url === 'string') {
return createMemoryHistory({
initialEntries: [url],
@@ -255,15 +252,6 @@ export const mockUseValuesList = (values?: ListItem[]) => {
return { spy, onRefreshTimeRange };
};
-export const mockUxSeries = {
- name: 'performance-distribution',
- dataType: 'ux',
- breakdown: 'user_agent.name',
- time: { from: 'now-15m', to: 'now' },
- reportDefinitions: { 'service.name': ['elastic-co'] },
- selectedMetricField: TRANSACTION_DURATION,
-} as SeriesUrl;
-
function mockSeriesStorageContext({
data,
filters,
@@ -273,34 +261,34 @@ function mockSeriesStorageContext({
filters?: UrlFilter[];
breakdown?: string;
}) {
- const testSeries = {
- ...mockUxSeries,
- breakdown: breakdown || 'user_agent.name',
- ...(filters ? { filters } : {}),
+ const mockDataSeries = data || {
+ 'performance-distribution': {
+ reportType: 'data-distribution',
+ dataType: 'ux',
+ breakdown: breakdown || 'user_agent.name',
+ time: { from: 'now-15m', to: 'now' },
+ ...(filters ? { filters } : {}),
+ },
};
+ const allSeriesIds = Object.keys(mockDataSeries);
+ const firstSeriesId = allSeriesIds?.[0];
- const mockDataSeries = data || [testSeries];
+ const series = mockDataSeries[firstSeriesId];
const removeSeries = jest.fn();
const setSeries = jest.fn();
- const getSeries = jest.fn().mockReturnValue(testSeries);
+ const getSeries = jest.fn().mockReturnValue(series);
return {
+ firstSeriesId,
+ allSeriesIds,
removeSeries,
setSeries,
getSeries,
- autoApply: true,
- reportType: 'data-distribution',
- lastRefresh: Date.now(),
- setLastRefresh: jest.fn(),
- setAutoApply: jest.fn(),
- applyChanges: jest.fn(),
- firstSeries: mockDataSeries[0],
+ firstSeries: mockDataSeries[firstSeriesId],
allSeries: mockDataSeries,
- setReportType: jest.fn(),
- storage: { get: jest.fn() } as any,
- } as SeriesContextValue;
+ };
}
export function mockUseSeriesFilter() {
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 85%
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 8f196b8a05dda..c054853d9c877 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,12 +7,12 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
-import { mockUxSeries, render } from '../../rtl_helpers';
+import { render } from '../../rtl_helpers';
import { SeriesChartTypesSelect, XYChartTypesSelect } from './chart_types';
describe.skip('SeriesChartTypesSelect', function () {
it('should render properly', async function () {
- render();
+ render();
await waitFor(() => {
screen.getByText(/chart type/i);
@@ -21,7 +21,7 @@ describe.skip('SeriesChartTypesSelect', function () {
it('should call set series on change', async function () {
const { setSeries } = render(
-
+
);
await waitFor(() => {
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_builder/columns/chart_types.tsx
similarity index 77%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
index 27d846502dbe6..50c2f91e6067d 100644
--- 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_builder/columns/chart_types.tsx
@@ -6,11 +6,11 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiSuperSelect } from '@elastic/eui';
+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 { SeriesUrl, useFetcher } from '../../../../..';
+import { useFetcher } from '../../../../..';
import { useSeriesStorage } from '../../hooks/use_series_storage';
import { SeriesType } from '../../../../../../../lens/public';
@@ -20,14 +20,16 @@ const CHART_TYPE_LABEL = i18n.translate('xpack.observability.expView.chartTypes.
export function SeriesChartTypesSelect({
seriesId,
- series,
+ seriesTypes,
defaultChartType,
}: {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
+ seriesTypes?: SeriesType[];
defaultChartType: SeriesType;
}) {
- const { setSeries } = useSeriesStorage();
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
const seriesType = series?.seriesType ?? defaultChartType;
@@ -40,15 +42,17 @@ export function SeriesChartTypesSelect({
onChange={onChange}
value={seriesType}
excludeChartTypes={['bar_percentage_stacked']}
- includeChartTypes={[
- 'bar',
- 'bar_horizontal',
- 'line',
- 'area',
- 'bar_stacked',
- 'area_stacked',
- 'bar_horizontal_percentage_stacked',
- ]}
+ includeChartTypes={
+ seriesTypes || [
+ 'bar',
+ 'bar_horizontal',
+ 'line',
+ 'area',
+ 'bar_stacked',
+ 'area_stacked',
+ 'bar_horizontal_percentage_stacked',
+ ]
+ }
label={CHART_TYPE_LABEL}
/>
);
@@ -101,14 +105,14 @@ export function XYChartTypesSelect({
});
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
new file mode 100644
index 0000000000000..b10702ebded57
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
@@ -0,0 +1,62 @@
+/*
+ * 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 { mockAppIndexPattern, render } from '../../rtl_helpers';
+import { dataTypes, DataTypesCol } from './data_types_col';
+
+describe('DataTypesCol', function () {
+ const seriesId = 'test-series-id';
+
+ mockAppIndexPattern();
+
+ it('should render properly', function () {
+ const { getByText } = render();
+
+ dataTypes.forEach(({ label }) => {
+ getByText(label);
+ });
+ });
+
+ it('should set series on change', function () {
+ const { setSeries } = render();
+
+ fireEvent.click(screen.getByText(/user experience \(rum\)/i));
+
+ expect(setSeries).toHaveBeenCalledTimes(1);
+ expect(setSeries).toHaveBeenCalledWith(seriesId, {
+ dataType: 'ux',
+ isNew: true,
+ time: {
+ from: 'now-15m',
+ to: 'now',
+ },
+ });
+ });
+
+ it('should set series on change on already selected', function () {
+ const initSeries = {
+ data: {
+ [seriesId]: {
+ dataType: 'synthetics' as const,
+ reportType: 'kpi-over-time' as const,
+ breakdown: 'monitor.status',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ };
+
+ render(, { initSeries });
+
+ const button = screen.getByRole('button', {
+ name: /Synthetic Monitoring/i,
+ });
+
+ expect(button.classList).toContain('euiButton--fill');
+ });
+});
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
new file mode 100644
index 0000000000000..f386f62d9ed73
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import styled from 'styled-components';
+import { AppDataType } from '../../types';
+import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+
+export const dataTypes: Array<{ id: AppDataType; label: string }> = [
+ { id: 'synthetics', label: 'Synthetic Monitoring' },
+ { id: 'ux', label: 'User Experience (RUM)' },
+ { id: 'mobile', label: 'Mobile Experience' },
+ // { id: 'infra_logs', label: 'Logs' },
+ // { id: 'infra_metrics', label: 'Metrics' },
+ // { id: 'apm', label: 'APM' },
+];
+
+export function DataTypesCol({ seriesId }: { seriesId: string }) {
+ const { getSeries, setSeries, removeSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+ const { loading } = useAppIndexPatternContext();
+
+ const onDataTypeChange = (dataType?: AppDataType) => {
+ if (!dataType) {
+ removeSeries(seriesId);
+ } else {
+ setSeries(seriesId || `${dataType}-series`, {
+ dataType,
+ isNew: true,
+ time: series.time,
+ } as any);
+ }
+ };
+
+ const selectedDataType = series.dataType;
+
+ return (
+
+ {dataTypes.map(({ id: dataTypeId, label }) => (
+
+
+
+ ))}
+
+ );
+}
+
+const FlexGroup = styled(EuiFlexGroup)`
+ width: 100%;
+`;
+
+const Button = styled(EuiButton)`
+ will-change: transform;
+`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx
new file mode 100644
index 0000000000000..6be78084ae195
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/date_picker_col.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 styled from 'styled-components';
+import { SeriesDatePicker } from '../../series_date_picker';
+import { DateRangePicker } from '../../series_date_picker/date_range_picker';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+
+interface Props {
+ seriesId: string;
+}
+export function DatePickerCol({ seriesId }: Props) {
+ const { firstSeriesId, getSeries } = useSeriesStorage();
+ const { reportType } = getSeries(firstSeriesId);
+
+ return (
+
+ {firstSeriesId === seriesId || reportType !== 'kpi-over-time' ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+const Wrapper = styled.div`
+ .euiSuperDatePicker__flexWrapper {
+ width: 100%;
+ > .euiFlexItem {
+ margin-right: 0px;
+ }
+ }
+`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx
similarity index 69%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx
index ced4d3af057ff..516f04e3812ba 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx
@@ -7,66 +7,62 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
-import { mockUxSeries, render } from '../../rtl_helpers';
+import { render } from '../../rtl_helpers';
import { OperationTypeSelect } from './operation_type_select';
describe('OperationTypeSelect', function () {
it('should render properly', function () {
- render();
+ render();
screen.getByText('Select an option: , is selected');
});
it('should display selected value', function () {
const initSeries = {
- data: [
- {
- name: 'performance-distribution',
+ data: {
+ 'performance-distribution': {
dataType: 'ux' as const,
+ reportType: 'kpi-over-time' as const,
operationType: 'median' as const,
time: { from: 'now-15m', to: 'now' },
},
- ],
+ },
};
- render(, {
- initSeries,
- });
+ render(, { initSeries });
screen.getByText('Median');
});
it('should call set series on change', function () {
const initSeries = {
- data: [
- {
- name: 'performance-distribution',
+ data: {
+ 'series-id': {
dataType: 'ux' as const,
+ reportType: 'kpi-over-time' as const,
operationType: 'median' as const,
time: { from: 'now-15m', to: 'now' },
},
- ],
+ },
};
- const { setSeries } = render(, {
- initSeries,
- });
+ const { setSeries } = render(, { initSeries });
fireEvent.click(screen.getByTestId('operationTypeSelect'));
- expect(setSeries).toHaveBeenCalledWith(0, {
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
operationType: 'median',
dataType: 'ux',
+ reportType: 'kpi-over-time',
time: { from: 'now-15m', to: 'now' },
- name: 'performance-distribution',
});
fireEvent.click(screen.getByText('95th Percentile'));
- expect(setSeries).toHaveBeenCalledWith(0, {
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
operationType: '95th',
dataType: 'ux',
+ reportType: 'kpi-over-time',
time: { from: 'now-15m', to: 'now' },
- name: 'performance-distribution',
});
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
similarity index 91%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
index 4c10c9311704d..fce1383f30f34 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/operation_type_select.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
@@ -11,18 +11,17 @@ import { EuiSuperSelect } from '@elastic/eui';
import { useSeriesStorage } from '../../hooks/use_series_storage';
import { OperationType } from '../../../../../../../lens/public';
-import { SeriesUrl } from '../../types';
export function OperationTypeSelect({
seriesId,
- series,
defaultOperationType,
}: {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
defaultOperationType?: OperationType;
}) {
- const { setSeries } = useSeriesStorage();
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
const operationType = series?.operationType;
@@ -84,7 +83,11 @@ export function OperationTypeSelect({
return (
);
+
+ screen.getByText('Select an option: , is selected');
+ screen.getAllByText('Browser family');
+ });
+
+ it('should set new series breakdown on change', function () {
+ const { setSeries } = render(
+
+ );
+
+ const btn = screen.getByRole('button', {
+ name: /select an option: Browser family , is selected/i,
+ hidden: true,
+ });
+
+ fireEvent.click(btn);
+
+ fireEvent.click(screen.getByText(/operating system/i));
+
+ expect(setSeries).toHaveBeenCalledTimes(1);
+ expect(setSeries).toHaveBeenCalledWith(seriesId, {
+ breakdown: USER_AGENT_OS,
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ });
+ });
+ it('should set undefined on new series on no select breakdown', function () {
+ const { setSeries } = render(
+
+ );
+
+ const btn = screen.getByRole('button', {
+ name: /select an option: Browser family , is selected/i,
+ hidden: true,
+ });
+
+ fireEvent.click(btn);
+
+ fireEvent.click(screen.getByText(/no breakdown/i));
+
+ expect(setSeries).toHaveBeenCalledTimes(1);
+ expect(setSeries).toHaveBeenCalledWith(seriesId, {
+ breakdown: undefined,
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ });
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx
new file mode 100644
index 0000000000000..fa2d01691ce1d
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 { Breakdowns } from '../../series_editor/columns/breakdowns';
+import { SeriesConfig } from '../../types';
+
+export function ReportBreakdowns({
+ seriesId,
+ seriesConfig,
+}: {
+ seriesConfig: SeriesConfig;
+ seriesId: string;
+}) {
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
similarity index 65%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
index 544a294e021e2..3d156e0ee9c2b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
@@ -12,14 +12,14 @@ import {
mockAppIndexPattern,
mockIndexPattern,
mockUseValuesList,
- mockUxSeries,
render,
} from '../../rtl_helpers';
import { ReportDefinitionCol } from './report_definition_col';
+import { SERVICE_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Series Builder ReportDefinitionCol', function () {
mockAppIndexPattern();
- const seriesId = 0;
+ const seriesId = 'test-series-id';
const seriesConfig = getDefaultConfigs({
reportType: 'data-distribution',
@@ -27,24 +27,36 @@ describe('Series Builder ReportDefinitionCol', function () {
dataType: 'ux',
});
+ const initSeries = {
+ data: {
+ [seriesId]: {
+ dataType: 'ux' as const,
+ reportType: 'data-distribution' as const,
+ time: { from: 'now-30d', to: 'now' },
+ reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] },
+ },
+ },
+ };
+
mockUseValuesList([{ label: 'elastic-co', count: 10 }]);
- it('renders', async () => {
- render(
-
- );
+ it('should render properly', async function () {
+ render(, {
+ initSeries,
+ });
await waitFor(() => {
- expect(screen.getByText('Web Application')).toBeInTheDocument();
- expect(screen.getByText('Environment')).toBeInTheDocument();
- expect(screen.getByText('Search Environment')).toBeInTheDocument();
+ screen.getByText('Web Application');
+ screen.getByText('Environment');
+ screen.getByText('Select an option: Page load time, is selected');
+ screen.getByText('Page load time');
});
});
it('should render selected report definitions', async function () {
- render(
-
- );
+ render(, {
+ initSeries,
+ });
expect(await screen.findByText('elastic-co')).toBeInTheDocument();
@@ -53,7 +65,8 @@ describe('Series Builder ReportDefinitionCol', function () {
it('should be able to remove selected definition', async function () {
const { setSeries } = render(
-
+ ,
+ { initSeries }
);
expect(
@@ -67,14 +80,11 @@ describe('Series Builder ReportDefinitionCol', function () {
fireEvent.click(removeBtn);
expect(setSeries).toHaveBeenCalledTimes(1);
-
expect(setSeries).toHaveBeenCalledWith(seriesId, {
dataType: 'ux',
- name: 'performance-distribution',
- breakdown: 'user_agent.name',
reportDefinitions: {},
- selectedMetricField: 'transaction.duration.us',
- time: { from: 'now-15m', to: 'now' },
+ reportType: 'data-distribution',
+ time: { from: 'now-30d', to: 'now' },
});
});
});
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
new file mode 100644
index 0000000000000..0c620abf56e8a
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
+import styled from 'styled-components';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+import { ReportMetricOptions } from '../report_metric_options';
+import { SeriesConfig } from '../../types';
+import { SeriesChartTypesSelect } from './chart_types';
+import { OperationTypeSelect } from './operation_type_select';
+import { DatePickerCol } from './date_picker_col';
+import { parseCustomFieldName } from '../../configurations/lens_attributes';
+import { ReportDefinitionField } from './report_definition_field';
+
+function getColumnType(seriesConfig: SeriesConfig, selectedMetricField?: string) {
+ const { columnType } = parseCustomFieldName(seriesConfig, selectedMetricField);
+
+ return columnType;
+}
+
+export function ReportDefinitionCol({
+ seriesConfig,
+ seriesId,
+}: {
+ seriesConfig: SeriesConfig;
+ seriesId: string;
+}) {
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
+ const { reportDefinitions: selectedReportDefinitions = {}, selectedMetricField } = series ?? {};
+
+ const {
+ definitionFields,
+ defaultSeriesType,
+ hasOperationType,
+ yAxisColumns,
+ metricOptions,
+ } = seriesConfig;
+
+ const onChange = (field: string, value?: string[]) => {
+ if (!value?.[0]) {
+ delete selectedReportDefinitions[field];
+ setSeries(seriesId, {
+ ...series,
+ reportDefinitions: { ...selectedReportDefinitions },
+ });
+ } else {
+ setSeries(seriesId, {
+ ...series,
+ reportDefinitions: { ...selectedReportDefinitions, [field]: value },
+ });
+ }
+ };
+
+ const columnType = getColumnType(seriesConfig, selectedMetricField);
+
+ return (
+
+
+
+
+
+ {definitionFields.map((field) => (
+
+
+
+ ))}
+ {metricOptions && (
+
+
+
+ )}
+ {(hasOperationType || columnType === 'operation') && (
+
+
+
+ )}
+
+
+
+
+ );
+}
+
+const FlexGroup = styled(EuiFlexGroup)`
+ width: 100%;
+`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx
similarity index 69%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx
index 3651b4b7f075b..8a83b5c2a8cb0 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx
@@ -6,25 +6,30 @@
*/
import React, { useMemo } from 'react';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isEmpty } from 'lodash';
import { ExistsFilter } from '@kbn/es-query';
import FieldValueSuggestions from '../../../field_value_suggestions';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { ESFilter } from '../../../../../../../../../src/core/types/elasticsearch';
import { PersistableFilter } from '../../../../../../../lens/common';
import { buildPhrasesFilter } from '../../configurations/utils';
-import { SeriesConfig, SeriesUrl } from '../../types';
+import { SeriesConfig } from '../../types';
import { ALL_VALUES_SELECTED } from '../../../field_value_suggestions/field_value_combobox';
interface Props {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
field: string;
seriesConfig: SeriesConfig;
onChange: (field: string, value?: string[]) => void;
}
-export function ReportDefinitionField({ series, field, seriesConfig, onChange }: Props) {
+export function ReportDefinitionField({ seriesId, field, seriesConfig, onChange }: Props) {
+ const { getSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
const { indexPattern } = useAppIndexPatternContext(series.dataType);
const { reportDefinitions: selectedReportDefinitions = {} } = series;
@@ -59,26 +64,23 @@ export function ReportDefinitionField({ series, field, seriesConfig, onChange }:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(selectedReportDefinitions), JSON.stringify(baseFilters)]);
- if (!indexPattern) {
- return null;
- }
-
return (
- onChange(field, val)}
- filters={queryFilters}
- time={series.time}
- fullWidth={true}
- asCombobox={true}
- allowExclusions={false}
- allowAllValuesSelection={true}
- usePrependLabel={false}
- compressed={false}
- required={isEmpty(selectedReportDefinitions)}
- />
+
+
+ {indexPattern && (
+ onChange(field, val)}
+ filters={queryFilters}
+ time={series.time}
+ fullWidth={true}
+ allowAllValuesSelection={true}
+ />
+ )}
+
+
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx
new file mode 100644
index 0000000000000..0b183b5f20c03
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { screen } from '@testing-library/react';
+import { ReportFilters } from './report_filters';
+import { getDefaultConfigs } from '../../configurations/default_configs';
+import { mockIndexPattern, render } from '../../rtl_helpers';
+
+describe('Series Builder ReportFilters', function () {
+ const seriesId = 'test-series-id';
+
+ const dataViewSeries = getDefaultConfigs({
+ reportType: 'data-distribution',
+ indexPattern: mockIndexPattern,
+ dataType: 'ux',
+ });
+
+ it('should render properly', function () {
+ render();
+
+ screen.getByText('Add filter');
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx
new file mode 100644
index 0000000000000..d5938c5387e8f
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 { SeriesFilter } from '../../series_editor/columns/series_filter';
+import { SeriesConfig } from '../../types';
+
+export function ReportFilters({
+ seriesConfig,
+ seriesId,
+}: {
+ seriesConfig: SeriesConfig;
+ seriesId: string;
+}) {
+ return (
+
+ );
+}
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
new file mode 100644
index 0000000000000..12ae8560453c9
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 { mockAppIndexPattern, 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 () {
+ const seriesId = 'performance-distribution';
+
+ mockAppIndexPattern();
+
+ it('should render properly', function () {
+ render();
+ screen.getByText('Performance distribution');
+ screen.getByText('KPI over time');
+ });
+
+ it('should display empty message', function () {
+ render();
+ screen.getByText(SELECTED_DATA_TYPE_FOR_REPORT);
+ });
+
+ it('should set series on change', function () {
+ const { setSeries } = render(
+
+ );
+
+ fireEvent.click(screen.getByText(/KPI over time/i));
+
+ expect(setSeries).toHaveBeenCalledWith(seriesId, {
+ dataType: 'ux',
+ selectedMetricField: undefined,
+ reportType: 'kpi-over-time',
+ time: { from: 'now-15m', to: 'now' },
+ });
+ expect(setSeries).toHaveBeenCalledTimes(1);
+ });
+
+ it('should set selected as filled', function () {
+ const initSeries = {
+ data: {
+ [seriesId]: {
+ dataType: 'synthetics' as const,
+ reportType: 'kpi-over-time' as const,
+ breakdown: 'monitor.status',
+ time: { from: 'now-15m', to: 'now' },
+ isNew: true,
+ },
+ },
+ };
+
+ const { setSeries } = render(
+ ,
+ { initSeries }
+ );
+
+ const button = screen.getByRole('button', {
+ name: /KPI over time/i,
+ });
+
+ expect(button.classList).toContain('euiButton--fill');
+ fireEvent.click(button);
+
+ // undefined on click selected
+ expect(setSeries).toHaveBeenCalledWith(seriesId, {
+ dataType: 'synthetics',
+ time: DEFAULT_TIME,
+ isNew: true,
+ });
+ });
+});
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
new file mode 100644
index 0000000000000..c4eebbfaca3eb
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx
@@ -0,0 +1,108 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { map } from 'lodash';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import styled from 'styled-components';
+import { ReportViewType, SeriesUrl } from '../../types';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+import { DEFAULT_TIME } from '../../configurations/constants';
+import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
+import { ReportTypeItem } from '../series_builder';
+
+interface Props {
+ seriesId: string;
+ reportTypes: ReportTypeItem[];
+}
+
+export function ReportTypesCol({ seriesId, reportTypes }: Props) {
+ const { setSeries, getSeries, firstSeries, firstSeriesId } = useSeriesStorage();
+
+ const { reportType: selectedReportType, ...restSeries } = getSeries(seriesId);
+
+ const { loading, hasData } = useAppIndexPatternContext(restSeries.dataType);
+
+ if (!restSeries.dataType) {
+ return (
+
+ );
+ }
+
+ if (!loading && !hasData) {
+ return (
+
+ );
+ }
+
+ const disabledReportTypes: ReportViewType[] = map(
+ reportTypes.filter(
+ ({ reportType }) => firstSeriesId !== seriesId && reportType !== firstSeries.reportType
+ ),
+ 'reportType'
+ );
+
+ return reportTypes?.length > 0 ? (
+
+ {reportTypes.map(({ reportType, label }) => (
+
+
+
+ ))}
+
+ ) : (
+ {SELECTED_DATA_TYPE_FOR_REPORT}
+ );
+}
+
+export const SELECTED_DATA_TYPE_FOR_REPORT = i18n.translate(
+ 'xpack.observability.expView.reportType.noDataType',
+ { defaultMessage: 'No data type selected.' }
+);
+
+const FlexGroup = styled(EuiFlexGroup)`
+ width: 100%;
+`;
+
+const Button = styled(EuiButton)`
+ will-change: transform;
+`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/last_updated.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/last_updated.tsx
similarity index 55%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/header/last_updated.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/last_updated.tsx
index c352ec0423dd8..874171de123d2 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/last_updated.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/last_updated.tsx
@@ -8,7 +8,6 @@
import React, { useEffect, useState } from 'react';
import { EuiIcon, EuiText } from '@elastic/eui';
import moment from 'moment';
-import { FormattedMessage } from '@kbn/i18n/react';
interface Props {
lastUpdated?: number;
@@ -19,34 +18,20 @@ export function LastUpdated({ lastUpdated }: Props) {
useEffect(() => {
const interVal = setInterval(() => {
setRefresh(Date.now());
- }, 5000);
+ }, 1000);
return () => {
clearInterval(interVal);
};
}, []);
- useEffect(() => {
- setRefresh(Date.now());
- }, [lastUpdated]);
-
if (!lastUpdated) {
return null;
}
- const isWarning = moment().diff(moment(lastUpdated), 'minute') > 5;
- const isDanger = moment().diff(moment(lastUpdated), 'minute') > 10;
-
return (
-
-
-
+
+ Last Updated: {moment(lastUpdated).from(refresh)}
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/report_metric_options.tsx
new file mode 100644
index 0000000000000..a2a3e34c21834
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/report_metric_options.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiSuperSelect } from '@elastic/eui';
+import { useSeriesStorage } from '../hooks/use_series_storage';
+import { SeriesConfig } from '../types';
+
+interface Props {
+ seriesId: string;
+ defaultValue?: string;
+ options: SeriesConfig['metricOptions'];
+}
+
+export function ReportMetricOptions({ seriesId, options: opts }: Props) {
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
+ const onChange = (value: string) => {
+ setSeries(seriesId, {
+ ...series,
+ selectedMetricField: value,
+ });
+ };
+
+ const options = opts ?? [];
+
+ return (
+ ({
+ value: fd || id,
+ inputDisplay: label,
+ }))}
+ valueOfSelected={series.selectedMetricField || options?.[0].field || options?.[0].id}
+ onChange={(value) => onChange(value)}
+ />
+ );
+}
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
new file mode 100644
index 0000000000000..684cf3a210a51
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
@@ -0,0 +1,303 @@
+/*
+ * 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, { RefObject, useEffect, useState } from 'react';
+import { isEmpty } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiBasicTable,
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiSwitch,
+} from '@elastic/eui';
+import { rgba } from 'polished';
+import { AppDataType, SeriesConfig, ReportViewType, SeriesUrl } from '../types';
+import { DataTypesCol } from './columns/data_types_col';
+import { ReportTypesCol } from './columns/report_types_col';
+import { ReportDefinitionCol } from './columns/report_definition_col';
+import { ReportFilters } from './columns/report_filters';
+import { ReportBreakdowns } from './columns/report_breakdowns';
+import { NEW_SERIES_KEY, useSeriesStorage } from '../hooks/use_series_storage';
+import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
+import { getDefaultConfigs } from '../configurations/default_configs';
+import { SeriesEditor } from '../series_editor/series_editor';
+import { SeriesActions } from '../series_editor/columns/series_actions';
+import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
+import { LastUpdated } from './last_updated';
+import {
+ CORE_WEB_VITALS_LABEL,
+ DEVICE_DISTRIBUTION_LABEL,
+ KPI_OVER_TIME_LABEL,
+ PERF_DIST_LABEL,
+} from '../configurations/constants/labels';
+
+export interface ReportTypeItem {
+ id: string;
+ reportType: ReportViewType;
+ label: string;
+}
+
+export const ReportTypes: Record = {
+ synthetics: [
+ { id: 'kpi', reportType: 'kpi-over-time', label: KPI_OVER_TIME_LABEL },
+ { id: 'dist', reportType: 'data-distribution', label: PERF_DIST_LABEL },
+ ],
+ ux: [
+ { id: 'kpi', reportType: 'kpi-over-time', label: KPI_OVER_TIME_LABEL },
+ { id: 'dist', reportType: 'data-distribution', label: PERF_DIST_LABEL },
+ { id: 'cwv', reportType: 'core-web-vitals', label: CORE_WEB_VITALS_LABEL },
+ ],
+ mobile: [
+ { id: 'kpi', reportType: 'kpi-over-time', label: KPI_OVER_TIME_LABEL },
+ { id: 'dist', reportType: 'data-distribution', label: PERF_DIST_LABEL },
+ { id: 'mdd', reportType: 'device-data-distribution', label: DEVICE_DISTRIBUTION_LABEL },
+ ],
+ apm: [],
+ infra_logs: [],
+ infra_metrics: [],
+};
+
+interface BuilderItem {
+ id: string;
+ series: SeriesUrl;
+ seriesConfig?: SeriesConfig;
+}
+
+export function SeriesBuilder({
+ seriesBuilderRef,
+ lastUpdated,
+ multiSeries,
+}: {
+ seriesBuilderRef: RefObject;
+ lastUpdated?: number;
+ multiSeries?: boolean;
+}) {
+ const [editorItems, setEditorItems] = useState([]);
+ const { getSeries, allSeries, allSeriesIds, setSeries, removeSeries } = useSeriesStorage();
+
+ const { loading, indexPatterns } = useAppIndexPatternContext();
+
+ useEffect(() => {
+ const getDataViewSeries = (dataType: AppDataType, reportType: SeriesUrl['reportType']) => {
+ if (indexPatterns?.[dataType]) {
+ return getDefaultConfigs({
+ dataType,
+ indexPattern: indexPatterns[dataType],
+ reportType: reportType!,
+ });
+ }
+ };
+
+ const seriesToEdit: BuilderItem[] =
+ allSeriesIds
+ .filter((sId) => {
+ return allSeries?.[sId]?.isNew;
+ })
+ .map((sId) => {
+ const series = getSeries(sId);
+ const seriesConfig = getDataViewSeries(series.dataType, series.reportType);
+
+ return { id: sId, series, seriesConfig };
+ }) ?? [];
+ const initSeries: BuilderItem[] = [{ id: 'series-id', series: {} as SeriesUrl }];
+ setEditorItems(multiSeries || seriesToEdit.length > 0 ? seriesToEdit : initSeries);
+ }, [allSeries, allSeriesIds, getSeries, indexPatterns, loading, multiSeries]);
+
+ const columns = [
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.dataType', {
+ defaultMessage: 'Data Type',
+ }),
+ field: 'id',
+ width: '15%',
+ render: (seriesId: string) => ,
+ },
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.report', {
+ defaultMessage: 'Report',
+ }),
+ width: '15%',
+ field: 'id',
+ render: (seriesId: string, { series: { dataType } }: BuilderItem) => (
+
+ ),
+ },
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.definition', {
+ defaultMessage: 'Definition',
+ }),
+ width: '30%',
+ field: 'id',
+ render: (
+ seriesId: string,
+ { series: { dataType, reportType }, seriesConfig }: BuilderItem
+ ) => {
+ if (dataType && seriesConfig) {
+ return loading ? (
+ LOADING_VIEW
+ ) : reportType ? (
+
+ ) : (
+ SELECT_REPORT_TYPE
+ );
+ }
+
+ return null;
+ },
+ },
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.filters', {
+ defaultMessage: 'Filters',
+ }),
+ width: '20%',
+ field: 'id',
+ render: (seriesId: string, { series: { reportType }, seriesConfig }: BuilderItem) =>
+ reportType && seriesConfig ? (
+
+ ) : null,
+ },
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.breakdown', {
+ defaultMessage: 'Breakdowns',
+ }),
+ width: '20%',
+ field: 'id',
+ render: (seriesId: string, { series: { reportType }, seriesConfig }: BuilderItem) =>
+ reportType && seriesConfig ? (
+
+ ) : null,
+ },
+ ...(multiSeries
+ ? [
+ {
+ name: i18n.translate('xpack.observability.expView.seriesBuilder.actions', {
+ defaultMessage: 'Actions',
+ }),
+ align: 'center' as const,
+ width: '10%',
+ field: 'id',
+ render: (seriesId: string, item: BuilderItem) => (
+
+ ),
+ },
+ ]
+ : []),
+ ];
+
+ const applySeries = () => {
+ editorItems.forEach(({ series, id: seriesId }) => {
+ const { reportType, reportDefinitions, isNew, ...restSeries } = series;
+
+ if (reportType && !isEmpty(reportDefinitions)) {
+ const reportDefId = Object.values(reportDefinitions ?? {})[0];
+ const newSeriesId = `${reportDefId}-${reportType}`;
+
+ const newSeriesN: SeriesUrl = {
+ ...restSeries,
+ reportType,
+ reportDefinitions,
+ };
+
+ setSeries(newSeriesId, newSeriesN);
+ removeSeries(seriesId);
+ }
+ });
+ };
+
+ const addSeries = () => {
+ const prevSeries = allSeries?.[allSeriesIds?.[0]];
+ setSeries(
+ `${NEW_SERIES_KEY}-${editorItems.length + 1}`,
+ prevSeries
+ ? ({ isNew: true, time: prevSeries.time } as SeriesUrl)
+ : ({ isNew: true } as SeriesUrl)
+ );
+ };
+
+ return (
+
+ {multiSeries && (
+
+
+
+
+
+ {}}
+ compressed
+ />
+
+
+ applySeries()} isDisabled={true} size="s">
+ {i18n.translate('xpack.observability.expView.seriesBuilder.apply', {
+ defaultMessage: 'Apply changes',
+ })}
+
+
+
+ addSeries()} size="s">
+ {i18n.translate('xpack.observability.expView.seriesBuilder.addSeries', {
+ defaultMessage: 'Add Series',
+ })}
+
+
+
+ )}
+
+ {multiSeries && }
+ {editorItems.length > 0 && (
+
+ )}
+
+
+
+ );
+}
+
+const Wrapper = euiStyled.div`
+ max-height: 50vh;
+ overflow-y: scroll;
+ overflow-x: clip;
+ &::-webkit-scrollbar {
+ height: ${({ theme }) => theme.eui.euiScrollBar};
+ width: ${({ theme }) => theme.eui.euiScrollBar};
+ }
+ &::-webkit-scrollbar-thumb {
+ background-clip: content-box;
+ background-color: ${({ theme }) => rgba(theme.eui.euiColorDarkShade, 0.5)};
+ border: ${({ theme }) => theme.eui.euiScrollBarCorner} solid transparent;
+ }
+ &::-webkit-scrollbar-corner,
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+`;
+
+export const LOADING_VIEW = i18n.translate(
+ 'xpack.observability.expView.seriesBuilder.loadingView',
+ {
+ defaultMessage: 'Loading view ...',
+ }
+);
+
+export const SELECT_REPORT_TYPE = i18n.translate(
+ 'xpack.observability.expView.seriesBuilder.selectReportType',
+ {
+ defaultMessage: 'No report type selected',
+ }
+);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/date_range_picker.tsx
similarity index 58%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/date_range_picker.tsx
index 0b8e1c1785c7f..c30863585b3b0 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/date_range_picker.tsx
@@ -6,48 +6,48 @@
*/
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { EuiDatePicker, EuiDatePickerRange } from '@elastic/eui';
-import { Moment } from 'moment';
import DateMath from '@elastic/datemath';
-import { i18n } from '@kbn/i18n';
+import { Moment } from 'moment';
import { useSeriesStorage } from '../hooks/use_series_storage';
import { useUiSetting } from '../../../../../../../../src/plugins/kibana_react/public';
-import { SeriesUrl } from '../types';
-import { ReportTypes } from '../configurations/constants';
export const parseAbsoluteDate = (date: string, options = {}) => {
return DateMath.parse(date, options)!;
};
-export function DateRangePicker({ seriesId, series }: { seriesId: number; series: SeriesUrl }) {
- const { firstSeries, setSeries, reportType } = useSeriesStorage();
+export function DateRangePicker({ seriesId }: { seriesId: string }) {
+ const { firstSeriesId, getSeries, setSeries } = useSeriesStorage();
const dateFormat = useUiSetting('dateFormat');
- const seriesFrom = series.time?.from;
- const seriesTo = series.time?.to;
+ const {
+ time: { from, to },
+ reportType,
+ } = getSeries(firstSeriesId);
- const { from: mainFrom, to: mainTo } = firstSeries!.time;
+ const series = getSeries(seriesId);
- const startDate = parseAbsoluteDate(seriesFrom ?? mainFrom)!;
- const endDate = parseAbsoluteDate(seriesTo ?? mainTo, { roundUp: true })!;
+ const {
+ time: { from: seriesFrom, to: seriesTo },
+ } = series;
- const getTotalDuration = () => {
- const mainStartDate = parseAbsoluteDate(mainTo)!;
- const mainEndDate = parseAbsoluteDate(mainTo, { roundUp: true })!;
- return mainEndDate.diff(mainStartDate, 'millisecond');
- };
+ const startDate = parseAbsoluteDate(seriesFrom ?? from)!;
+ const endDate = parseAbsoluteDate(seriesTo ?? to, { roundUp: true })!;
- const onStartChange = (newStartDate: Moment) => {
- if (reportType === ReportTypes.KPI) {
- const totalDuration = getTotalDuration();
- const newFrom = newStartDate.toISOString();
- const newTo = newStartDate.add(totalDuration, 'millisecond').toISOString();
+ const onStartChange = (newDate: Moment) => {
+ if (reportType === 'kpi-over-time') {
+ const mainStartDate = parseAbsoluteDate(from)!;
+ const mainEndDate = parseAbsoluteDate(to, { roundUp: true })!;
+ const totalDuration = mainEndDate.diff(mainStartDate, 'millisecond');
+ const newFrom = newDate.toISOString();
+ const newTo = newDate.add(totalDuration, 'millisecond').toISOString();
setSeries(seriesId, {
...series,
time: { from: newFrom, to: newTo },
});
} else {
- const newFrom = newStartDate.toISOString();
+ const newFrom = newDate.toISOString();
setSeries(seriesId, {
...series,
@@ -55,19 +55,20 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series
});
}
};
-
- const onEndChange = (newEndDate: Moment) => {
- if (reportType === ReportTypes.KPI) {
- const totalDuration = getTotalDuration();
- const newTo = newEndDate.toISOString();
- const newFrom = newEndDate.subtract(totalDuration, 'millisecond').toISOString();
+ const onEndChange = (newDate: Moment) => {
+ if (reportType === 'kpi-over-time') {
+ const mainStartDate = parseAbsoluteDate(from)!;
+ const mainEndDate = parseAbsoluteDate(to, { roundUp: true })!;
+ const totalDuration = mainEndDate.diff(mainStartDate, 'millisecond');
+ const newTo = newDate.toISOString();
+ const newFrom = newDate.subtract(totalDuration, 'millisecond').toISOString();
setSeries(seriesId, {
...series,
time: { from: newFrom, to: newTo },
});
} else {
- const newTo = newEndDate.toISOString();
+ const newTo = newDate.toISOString();
setSeries(seriesId, {
...series,
@@ -89,7 +90,7 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series
aria-label={i18n.translate('xpack.observability.expView.dateRanger.startDate', {
defaultMessage: 'Start date',
})}
- dateFormat={dateFormat.replace('ss.SSS', 'ss')}
+ dateFormat={dateFormat}
showTimeSelect
/>
}
@@ -103,7 +104,7 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series
aria-label={i18n.translate('xpack.observability.expView.dateRanger.endDate', {
defaultMessage: 'End date',
})}
- dateFormat={dateFormat.replace('ss.SSS', 'ss')}
+ dateFormat={dateFormat}
showTimeSelect
/>
}
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
new file mode 100644
index 0000000000000..e21da424b58c8
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 { EuiSuperDatePicker } from '@elastic/eui';
+import React, { useEffect } from 'react';
+import { useHasData } from '../../../../hooks/use_has_data';
+import { useSeriesStorage } from '../hooks/use_series_storage';
+import { useQuickTimeRanges } from '../../../../hooks/use_quick_time_ranges';
+import { DEFAULT_TIME } from '../configurations/constants';
+
+export interface TimePickerTime {
+ from: string;
+ to: string;
+}
+
+export interface TimePickerQuickRange extends TimePickerTime {
+ display: string;
+}
+
+interface Props {
+ seriesId: string;
+}
+
+export function SeriesDatePicker({ seriesId }: Props) {
+ const { onRefreshTimeRange } = useHasData();
+
+ const commonlyUsedRanges = useQuickTimeRanges();
+
+ const { getSeries, setSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
+ function onTimeChange({ start, end }: { start: string; end: string }) {
+ onRefreshTimeRange();
+ setSeries(seriesId, { ...series, time: { from: start, to: end } });
+ }
+
+ useEffect(() => {
+ if (!series || !series.time) {
+ setSeries(seriesId, { ...series, time: DEFAULT_TIME });
+ }
+ }, [series, seriesId, setSeries]);
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/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
similarity index 50%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/components/series_date_picker/series_date_picker.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
index 3517508300e4b..931dfbe07cd23 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/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
@@ -6,48 +6,67 @@
*/
import React from 'react';
-import { mockUseHasData, render } from '../../rtl_helpers';
+import { 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 () {
const initSeries = {
- data: [
- {
- name: 'uptime-pings-histogram',
+ data: {
+ 'uptime-pings-histogram': {
dataType: 'synthetics' as const,
+ reportType: 'data-distribution' as const,
breakdown: 'monitor.status',
time: { from: 'now-30m', to: 'now' },
},
- ],
+ },
};
- const { getByText } = render(, {
- initSeries,
- });
+ const { getByText } = render(, { initSeries });
- getByText('Last 30 Minutes');
+ getByText('Last 30 minutes');
+ });
+
+ it('should set defaults', async function () {
+ const initSeries = {
+ data: {
+ 'uptime-pings-histogram': {
+ reportType: 'kpi-over-time' as const,
+ dataType: 'synthetics' as const,
+ breakdown: 'monitor.status',
+ },
+ },
+ };
+ const { setSeries: setSeries1 } = render(
+ ,
+ { initSeries: initSeries as any }
+ );
+ expect(setSeries1).toHaveBeenCalledTimes(1);
+ expect(setSeries1).toHaveBeenCalledWith('uptime-pings-histogram', {
+ breakdown: 'monitor.status',
+ dataType: 'synthetics' as const,
+ reportType: 'kpi-over-time' as const,
+ time: DEFAULT_TIME,
+ });
});
it('should set series data', async function () {
const initSeries = {
- data: [
- {
- name: 'uptime-pings-histogram',
+ data: {
+ 'uptime-pings-histogram': {
dataType: 'synthetics' as const,
+ reportType: 'kpi-over-time' as const,
breakdown: 'monitor.status',
time: { from: 'now-30m', to: 'now' },
},
- ],
+ },
};
const { onRefreshTimeRange } = mockUseHasData();
- const { getByTestId, setSeries } = render(
- ,
- {
- initSeries,
- }
- );
+ const { getByTestId, setSeries } = render(, {
+ initSeries,
+ });
await waitFor(function () {
fireEvent.click(getByTestId('superDatePickerToggleQuickMenuButton'));
@@ -57,10 +76,10 @@ describe('SeriesDatePicker', function () {
expect(onRefreshTimeRange).toHaveBeenCalledTimes(1);
- expect(setSeries).toHaveBeenCalledWith(0, {
- name: 'uptime-pings-histogram',
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
breakdown: 'monitor.status',
dataType: 'synthetics',
+ reportType: 'kpi-over-time',
time: { from: 'now/d', to: 'now/d' },
});
expect(setSeries).toHaveBeenCalledTimes(1);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx
new file mode 100644
index 0000000000000..207a53e13f1ad
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx
@@ -0,0 +1,30 @@
+/*
+ * 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 } from '@elastic/eui';
+import { Breakdowns } from './columns/breakdowns';
+import { SeriesConfig } from '../types';
+import { ChartOptions } from './columns/chart_options';
+
+interface Props {
+ seriesConfig: SeriesConfig;
+ seriesId: string;
+ breakdownFields: string[];
+}
+export function ChartEditOptions({ seriesConfig, seriesId, breakdownFields }: Props) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
similarity index 74%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
index 21b766227a562..84568e1c5068a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { Breakdowns } from './breakdowns';
-import { mockIndexPattern, mockUxSeries, render } from '../../rtl_helpers';
+import { mockIndexPattern, render } from '../../rtl_helpers';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fieldnames';
@@ -20,7 +20,13 @@ describe('Breakdowns', function () {
});
it('should render properly', async function () {
- render();
+ render(
+
+ );
screen.getAllByText('Browser family');
});
@@ -30,9 +36,9 @@ describe('Breakdowns', function () {
const { setSeries } = render(
,
{ initSeries }
);
@@ -43,14 +49,10 @@ describe('Breakdowns', function () {
fireEvent.click(screen.getByText('Browser family'));
- expect(setSeries).toHaveBeenCalledWith(0, {
+ expect(setSeries).toHaveBeenCalledWith('series-id', {
breakdown: 'user_agent.name',
dataType: 'ux',
- name: 'performance-distribution',
- reportDefinitions: {
- 'service.name': ['elastic-co'],
- },
- selectedMetricField: 'transaction.duration.us',
+ reportType: 'data-distribution',
time: { from: 'now-15m', to: 'now' },
});
expect(setSeries).toHaveBeenCalledTimes(1);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
similarity index 71%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
index 315f63e33bed0..2237935d466ad 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/breakdowns.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
@@ -8,20 +8,20 @@
import React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { useRouteMatch } from 'react-router-dom';
import { useSeriesStorage } from '../../hooks/use_series_storage';
import { USE_BREAK_DOWN_COLUMN } from '../../configurations/constants';
-import { SeriesConfig, SeriesUrl } from '../../types';
+import { SeriesConfig } from '../../types';
interface Props {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
+ breakdowns: string[];
seriesConfig: SeriesConfig;
}
-export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
- const { setSeries } = useSeriesStorage();
- const isPreview = !!useRouteMatch('/exploratory-view/preview');
+export function Breakdowns({ seriesConfig, seriesId, breakdowns = [] }: Props) {
+ const { setSeries, getSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
const selectedBreakdown = series.breakdown;
const NO_BREAKDOWN = 'no_breakdown';
@@ -40,13 +40,9 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
}
};
- if (!seriesConfig) {
- return null;
- }
-
const hasUseBreakdownColumn = seriesConfig.xAxisColumn.sourceField === USE_BREAK_DOWN_COLUMN;
- const items = seriesConfig.breakdownFields.map((breakdown) => ({
+ const items = breakdowns.map((breakdown) => ({
id: breakdown,
label: seriesConfig.labels[breakdown],
}));
@@ -54,12 +50,14 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
if (!hasUseBreakdownColumn) {
items.push({
id: NO_BREAKDOWN,
- label: NO_BREAK_DOWN_LABEL,
+ label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', {
+ defaultMessage: 'No breakdown',
+ }),
});
}
const options = items.map(({ id, label }) => ({
- inputDisplay: label,
+ inputDisplay: id === NO_BREAKDOWN ? label : {label},
value: id,
dropdownDisplay: label,
}));
@@ -71,7 +69,7 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
onOptionChange(value)}
@@ -80,10 +78,3 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
);
}
-
-export const NO_BREAK_DOWN_LABEL = i18n.translate(
- 'xpack.observability.exp.breakDownFilter.noBreakdown',
- {
- defaultMessage: 'No breakdown',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx
new file mode 100644
index 0000000000000..f2a6377fd9b71
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 } from '@elastic/eui';
+import { SeriesConfig } from '../../types';
+import { OperationTypeSelect } from '../../series_builder/columns/operation_type_select';
+import { SeriesChartTypesSelect } from '../../series_builder/columns/chart_types';
+
+interface Props {
+ seriesConfig: SeriesConfig;
+ seriesId: string;
+}
+
+export function ChartOptions({ seriesConfig, seriesId }: Props) {
+ return (
+
+
+
+
+ {seriesConfig.hasOperationType && (
+
+
+
+ )}
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.test.tsx
deleted file mode 100644
index 838631e1f05df..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 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 { mockAppIndexPattern, mockUxSeries, render } from '../../rtl_helpers';
-import { DataTypesLabels, DataTypesSelect } from './data_type_select';
-import { DataTypes } from '../../configurations/constants';
-
-describe('DataTypeSelect', function () {
- const seriesId = 0;
-
- mockAppIndexPattern();
-
- it('should render properly', function () {
- render();
- });
-
- it('should set series on change', async function () {
- const { setSeries } = render();
-
- fireEvent.click(await screen.findByText(DataTypesLabels[DataTypes.UX]));
- fireEvent.click(await screen.findByText(DataTypesLabels[DataTypes.SYNTHETICS]));
-
- expect(setSeries).toHaveBeenCalledTimes(1);
- expect(setSeries).toHaveBeenCalledWith(seriesId, {
- dataType: 'synthetics',
- name: 'synthetics-series-1',
- time: {
- from: 'now-15m',
- to: 'now',
- },
- });
- });
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.tsx
deleted file mode 100644
index b0a6e3b5e26b0..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/data_type_select.tsx
+++ /dev/null
@@ -1,105 +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 { EuiSuperSelect } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { AppDataType, SeriesUrl } from '../../types';
-import { DataTypes, ReportTypes } from '../../configurations/constants';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
-}
-
-export const DataTypesLabels = {
- [DataTypes.UX]: i18n.translate('xpack.observability.overview.exploratoryView.uxLabel', {
- defaultMessage: 'User experience (RUM)',
- }),
-
- [DataTypes.SYNTHETICS]: i18n.translate(
- 'xpack.observability.overview.exploratoryView.syntheticsLabel',
- {
- defaultMessage: 'Synthetics monitoring',
- }
- ),
-
- [DataTypes.MOBILE]: i18n.translate(
- 'xpack.observability.overview.exploratoryView.mobileExperienceLabel',
- {
- defaultMessage: 'Mobile experience',
- }
- ),
-};
-
-export const dataTypes: Array<{ id: AppDataType; label: string }> = [
- {
- id: DataTypes.SYNTHETICS,
- label: DataTypesLabels[DataTypes.SYNTHETICS],
- },
- {
- id: DataTypes.UX,
- label: DataTypesLabels[DataTypes.UX],
- },
- {
- id: DataTypes.MOBILE,
- label: DataTypesLabels[DataTypes.MOBILE],
- },
-];
-
-const SELECT_DATA_TYPE = 'SELECT_DATA_TYPE';
-
-export function DataTypesSelect({ seriesId, series }: Props) {
- const { setSeries, reportType } = useSeriesStorage();
-
- const onDataTypeChange = (dataType: AppDataType) => {
- if (String(dataType) !== SELECT_DATA_TYPE) {
- setSeries(seriesId, {
- dataType,
- time: series.time,
- name: `${dataType}-series-${seriesId + 1}`,
- });
- }
- };
-
- const options = dataTypes
- .filter(({ id }) => {
- if (reportType === ReportTypes.DEVICE_DISTRIBUTION) {
- return id === DataTypes.MOBILE;
- }
- if (reportType === ReportTypes.CORE_WEB_VITAL) {
- return id === DataTypes.UX;
- }
- return true;
- })
- .map(({ id, label }) => ({
- value: id,
- inputDisplay: label,
- }));
-
- return (
- onDataTypeChange(value as AppDataType)}
- style={{ minWidth: 220 }}
- />
- );
-}
-
-const SELECT_DATA_TYPE_LABEL = i18n.translate(
- 'xpack.observability.overview.exploratoryView.selectDataType',
- {
- defaultMessage: 'Select data type',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx
index 032eb66dcfa4f..41e83f407af2b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/date_picker_col.tsx
@@ -6,84 +6,24 @@
*/
import React from 'react';
-import styled from 'styled-components';
-import { i18n } from '@kbn/i18n';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { SeriesDatePicker } from '../../series_date_picker';
import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { DateRangePicker } from '../../components/date_range_picker';
-import { SeriesDatePicker } from '../../components/series_date_picker';
-import { AppDataType, SeriesUrl } from '../../types';
-import { ReportTypes } from '../../configurations/constants';
-import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
-import { SyntheticsAddData } from '../../../add_data_buttons/synthetics_add_data';
-import { MobileAddData } from '../../../add_data_buttons/mobile_add_data';
-import { UXAddData } from '../../../add_data_buttons/ux_add_data';
+import { DateRangePicker } from '../../series_date_picker/date_range_picker';
interface Props {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
}
-
-const AddDataComponents: Record = {
- mobile: MobileAddData,
- ux: UXAddData,
- synthetics: SyntheticsAddData,
- apm: null,
- infra_logs: null,
- infra_metrics: null,
-};
-
-export function DatePickerCol({ seriesId, series }: Props) {
- const { reportType } = useSeriesStorage();
-
- const { hasAppData } = useAppIndexPatternContext();
-
- if (!series.dataType) {
- return null;
- }
-
- const AddDataButton = AddDataComponents[series.dataType];
- if (hasAppData[series.dataType] === false && AddDataButton !== null) {
- return (
-
-
-
- {i18n.translate('xpack.observability.overview.exploratoryView.noDataAvailable', {
- defaultMessage: 'No {dataType} data available.',
- values: {
- dataType: series.dataType,
- },
- })}
-
-
-
-
-
-
- );
- }
-
- if (!series.selectedMetricField) {
- return null;
- }
+export function DatePickerCol({ seriesId }: Props) {
+ const { firstSeriesId, getSeries } = useSeriesStorage();
+ const { reportType } = getSeries(firstSeriesId);
return (
-
- {seriesId === 0 || reportType !== ReportTypes.KPI ? (
-
+
+ {firstSeriesId === seriesId || reportType !== 'kpi-over-time' ? (
+
) : (
-
+
)}
-
+
);
}
-
-const Wrapper = styled.div`
- width: 100%;
- .euiSuperDatePicker__flexWrapper {
- width: 100%;
- > .euiFlexItem {
- margin-right: 0;
- }
- }
-`;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx
similarity index 67%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx
index a88e2eadd10c9..90a039f6b44d0 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx
@@ -8,24 +8,20 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { FilterExpanded } from './filter_expanded';
-import { mockUxSeries, mockAppIndexPattern, mockUseValuesList, render } from '../../rtl_helpers';
+import { mockAppIndexPattern, mockUseValuesList, render } from '../../rtl_helpers';
import { USER_AGENT_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
describe('FilterExpanded', function () {
- const filters = [{ field: USER_AGENT_NAME, values: ['Chrome'] }];
-
- const mockSeries = { ...mockUxSeries, filters };
-
- it('render', async () => {
- const initSeries = { filters };
+ it('should render properly', async function () {
+ const initSeries = { filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] };
mockAppIndexPattern();
render(
,
{ initSeries }
@@ -37,14 +33,15 @@ describe('FilterExpanded', function () {
});
it('should call go back on click', async function () {
- const initSeries = { filters };
+ const initSeries = { filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] };
+ const goBack = jest.fn();
render(
,
{ initSeries }
@@ -52,23 +49,28 @@ describe('FilterExpanded', function () {
await waitFor(() => {
fireEvent.click(screen.getByText('Browser Family'));
+
+ expect(goBack).toHaveBeenCalledTimes(1);
+ expect(goBack).toHaveBeenCalledWith();
});
});
- it('calls useValuesList on load', async () => {
- const initSeries = { filters };
+ it('should call useValuesList on load', async function () {
+ const initSeries = { filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] };
const { spy } = mockUseValuesList([
{ label: 'Chrome', count: 10 },
{ label: 'Firefox', count: 5 },
]);
+ const goBack = jest.fn();
+
render(
,
{ initSeries }
@@ -85,8 +87,8 @@ describe('FilterExpanded', function () {
});
});
- it('filters display values', async () => {
- const initSeries = { filters };
+ it('should filter display values', async function () {
+ const initSeries = { filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] };
mockUseValuesList([
{ label: 'Chrome', count: 10 },
@@ -95,20 +97,18 @@ describe('FilterExpanded', function () {
render(
,
{ initSeries }
);
- await waitFor(() => {
- fireEvent.click(screen.getByText('Browser Family'));
-
- expect(screen.queryByText('Firefox')).toBeTruthy();
+ expect(screen.getByText('Firefox')).toBeTruthy();
+ await waitFor(() => {
fireEvent.input(screen.getByRole('searchbox'), { target: { value: 'ch' } });
expect(screen.queryByText('Firefox')).toBeFalsy();
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx
similarity index 55%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx
index 1ef25722aff5c..4310402a43a08 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_expanded.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx
@@ -6,14 +6,7 @@
*/
import React, { useState, Fragment } from 'react';
-import {
- EuiFieldSearch,
- EuiSpacer,
- EuiFilterGroup,
- EuiText,
- EuiPopover,
- EuiFilterButton,
-} from '@elastic/eui';
+import { EuiFieldSearch, EuiSpacer, EuiButtonEmpty, EuiFilterGroup, EuiText } from '@elastic/eui';
import styled from 'styled-components';
import { rgba } from 'polished';
import { i18n } from '@kbn/i18n';
@@ -21,7 +14,8 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types';
import { map } from 'lodash';
import { ExistsFilter } from '@kbn/es-query';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
-import { SeriesConfig, SeriesUrl, UrlFilter } from '../../types';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+import { SeriesConfig, UrlFilter } from '../../types';
import { FilterValueButton } from './filter_value_btn';
import { useValuesList } from '../../../../../hooks/use_values_list';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
@@ -29,33 +23,31 @@ import { ESFilter } from '../../../../../../../../../src/core/types/elasticsearc
import { PersistableFilter } from '../../../../../../../lens/common';
interface Props {
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
label: string;
field: string;
isNegated?: boolean;
+ goBack: () => void;
nestedField?: string;
filters: SeriesConfig['baseFilters'];
}
-export interface NestedFilterOpen {
- value: string;
- negate: boolean;
-}
-
export function FilterExpanded({
seriesId,
- series,
field,
label,
+ goBack,
nestedField,
isNegated,
filters: defaultFilters,
}: Props) {
const [value, setValue] = useState('');
- const [isOpen, setIsOpen] = useState(false);
- const [isNestedOpen, setIsNestedOpen] = useState({ value: '', negate: false });
+ const [isOpen, setIsOpen] = useState({ value: '', negate: false });
+
+ const { getSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
const queryFilters: ESFilter[] = [];
@@ -89,71 +81,62 @@ export function FilterExpanded({
);
return (
- setIsOpen((prevState) => !prevState)} iconType="arrowDown">
- {label}
-
- }
- isOpen={isOpen}
- closePopover={() => setIsOpen(false)}
- >
-
- {
- setValue(evt.target.value);
- }}
- placeholder={i18n.translate('xpack.observability.filters.expanded.search', {
- defaultMessage: 'Search for {label}',
- values: { label },
- })}
- />
-
-
- {displayValues.length === 0 && !loading && (
-
- {i18n.translate('xpack.observability.filters.expanded.noFilter', {
- defaultMessage: 'No filters found.',
- })}
-
- )}
- {displayValues.map((opt) => (
-
-
- {isNegated !== false && (
-
- )}
+
+ goBack()}>
+ {label}
+
+ {
+ setValue(evt.target.value);
+ }}
+ placeholder={i18n.translate('xpack.observability.filters.expanded.search', {
+ defaultMessage: 'Search for {label}',
+ values: { label },
+ })}
+ />
+
+
+ {displayValues.length === 0 && !loading && (
+
+ {i18n.translate('xpack.observability.filters.expanded.noFilter', {
+ defaultMessage: 'No filters found.',
+ })}
+
+ )}
+ {displayValues.map((opt) => (
+
+
+ {isNegated !== false && (
-
-
-
- ))}
-
-
-
+ )}
+
+
+
+
+ ))}
+
+
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx
similarity index 64%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx
index 764a27fd663f5..a9609abc70d69 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.test.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { FilterValueButton } from './filter_value_btn';
-import { mockUxSeries, mockUseSeriesFilter, mockUseValuesList, render } from '../../rtl_helpers';
+import { mockUseSeriesFilter, mockUseValuesList, render } from '../../rtl_helpers';
import {
USER_AGENT_NAME,
USER_AGENT_VERSION,
@@ -19,98 +19,84 @@ describe('FilterValueButton', function () {
render(
);
- await waitFor(() => {
- expect(screen.getByText('Chrome')).toBeInTheDocument();
- });
+ screen.getByText('Chrome');
});
- describe('when negate is true', () => {
- it('displays negate stats', async () => {
- render(
-
- );
+ it('should render display negate state', async function () {
+ render(
+
+ );
- await waitFor(() => {
- expect(screen.getByText('Not Chrome')).toBeInTheDocument();
- expect(screen.getByTitle('Not Chrome')).toBeInTheDocument();
- const btn = screen.getByRole('button');
- expect(btn.classList).toContain('euiButtonEmpty--danger');
- });
+ await waitFor(() => {
+ screen.getByText('Not Chrome');
+ screen.getByTitle('Not Chrome');
+ const btn = screen.getByRole('button');
+ expect(btn.classList).toContain('euiButtonEmpty--danger');
});
+ });
- it('calls setFilter on click', async () => {
- const { setFilter, removeFilter } = mockUseSeriesFilter();
+ it('should call set filter on click', async function () {
+ const { setFilter, removeFilter } = mockUseSeriesFilter();
- render(
-
- );
+ render(
+
+ );
+ await waitFor(() => {
fireEvent.click(screen.getByText('Not Chrome'));
-
- await waitFor(() => {
- expect(removeFilter).toHaveBeenCalledTimes(0);
- expect(setFilter).toHaveBeenCalledTimes(1);
-
- expect(setFilter).toHaveBeenCalledWith({
- field: 'user_agent.name',
- negate: true,
- value: 'Chrome',
- });
+ expect(removeFilter).toHaveBeenCalledTimes(0);
+ expect(setFilter).toHaveBeenCalledTimes(1);
+ expect(setFilter).toHaveBeenCalledWith({
+ field: 'user_agent.name',
+ negate: true,
+ value: 'Chrome',
});
});
});
- describe('when selected', () => {
- it('removes the filter on click', async () => {
- const { removeFilter } = mockUseSeriesFilter();
-
- render(
-
- );
+ it('should remove filter on click if already selected', async function () {
+ const { removeFilter } = mockUseSeriesFilter();
+ render(
+
+ );
+ await waitFor(() => {
fireEvent.click(screen.getByText('Chrome'));
-
- await waitFor(() => {
- expect(removeFilter).toHaveBeenCalledWith({
- field: 'user_agent.name',
- negate: false,
- value: 'Chrome',
- });
+ expect(removeFilter).toHaveBeenCalledWith({
+ field: 'user_agent.name',
+ negate: false,
+ value: 'Chrome',
});
});
});
@@ -121,13 +107,12 @@ describe('FilterValueButton', function () {
render(
);
@@ -149,14 +134,13 @@ describe('FilterValueButton', function () {
render(
);
@@ -183,14 +167,13 @@ describe('FilterValueButton', function () {
render(
);
@@ -220,14 +203,13 @@ describe('FilterValueButton', function () {
render(
);
@@ -247,14 +229,13 @@ describe('FilterValueButton', function () {
render(
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx
similarity index 92%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx
index 111f915a95f46..bf4ca6eb83d94 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/filter_value_btn.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx
@@ -8,11 +8,10 @@ import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { EuiFilterButton, hexToRgb } from '@elastic/eui';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
import { useSeriesFilters } from '../../hooks/use_series_filters';
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import FieldValueSuggestions from '../../../field_value_suggestions';
-import { SeriesUrl } from '../../types';
-import { NestedFilterOpen } from './filter_expanded';
interface Props {
value: string;
@@ -20,13 +19,12 @@ interface Props {
allSelectedValues?: string[];
negate: boolean;
nestedField?: string;
- seriesId: number;
- series: SeriesUrl;
+ seriesId: string;
isNestedOpen: {
value: string;
negate: boolean;
};
- setIsNestedOpen: (val: NestedFilterOpen) => void;
+ setIsNestedOpen: (val: { value: string; negate: boolean }) => void;
}
export function FilterValueButton({
@@ -36,13 +34,16 @@ export function FilterValueButton({
field,
negate,
seriesId,
- series,
nestedField,
allSelectedValues,
}: Props) {
+ const { getSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
const { indexPatterns } = useAppIndexPatternContext(series.dataType);
- const { setFilter, removeFilter } = useSeriesFilters({ seriesId, series });
+ const { setFilter, removeFilter } = useSeriesFilters({ seriesId });
const hasActiveFilters = (allSelectedValues ?? []).includes(value);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx
new file mode 100644
index 0000000000000..e75f308dab1e5
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/remove_series.tsx
@@ -0,0 +1,34 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import React from 'react';
+import { EuiButtonIcon } from '@elastic/eui';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+
+interface Props {
+ seriesId: string;
+}
+
+export function RemoveSeries({ seriesId }: Props) {
+ const { removeSeries } = useSeriesStorage();
+
+ const onClick = () => {
+ removeSeries(seriesId);
+ };
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx
deleted file mode 100644
index dad2a7da2367b..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx
+++ /dev/null
@@ -1,59 +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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { SeriesConfig, SeriesUrl } from '../../types';
-import { ReportDefinitionField } from './report_definition_field';
-
-export function ReportDefinitionCol({
- seriesId,
- series,
- seriesConfig,
-}: {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}) {
- const { setSeries } = useSeriesStorage();
-
- const { reportDefinitions: selectedReportDefinitions = {} } = series;
-
- const { definitionFields } = seriesConfig;
-
- const onChange = (field: string, value?: string[]) => {
- if (!value?.[0]) {
- delete selectedReportDefinitions[field];
- setSeries(seriesId, {
- ...series,
- reportDefinitions: { ...selectedReportDefinitions },
- });
- } else {
- setSeries(seriesId, {
- ...series,
- reportDefinitions: { ...selectedReportDefinitions, [field]: value },
- });
- }
- };
-
- return (
-
- {definitionFields.map((field) => (
-
-
-
- ))}
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_type_select.tsx
deleted file mode 100644
index 01c9fce7637bb..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_type_select.tsx
+++ /dev/null
@@ -1,64 +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 { EuiSuperSelect } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { ReportViewType } from '../../types';
-import {
- CORE_WEB_VITALS_LABEL,
- DEVICE_DISTRIBUTION_LABEL,
- KPI_OVER_TIME_LABEL,
- PERF_DIST_LABEL,
-} from '../../configurations/constants/labels';
-
-const SELECT_REPORT_TYPE = 'SELECT_REPORT_TYPE';
-
-export const reportTypesList: Array<{
- reportType: ReportViewType | typeof SELECT_REPORT_TYPE;
- label: string;
-}> = [
- {
- reportType: SELECT_REPORT_TYPE,
- label: i18n.translate('xpack.observability.expView.reportType.selectLabel', {
- defaultMessage: 'Select report type',
- }),
- },
- { reportType: 'kpi-over-time', label: KPI_OVER_TIME_LABEL },
- { reportType: 'data-distribution', label: PERF_DIST_LABEL },
- { reportType: 'core-web-vitals', label: CORE_WEB_VITALS_LABEL },
- { reportType: 'device-data-distribution', label: DEVICE_DISTRIBUTION_LABEL },
-];
-
-export function ReportTypesSelect() {
- const { setReportType, reportType: selectedReportType, allSeries } = useSeriesStorage();
-
- const onReportTypeChange = (reportType: ReportViewType) => {
- setReportType(reportType);
- };
-
- const options = reportTypesList
- .filter(({ reportType }) => (selectedReportType ? reportType !== SELECT_REPORT_TYPE : true))
- .map(({ reportType, label }) => ({
- value: reportType,
- inputDisplay: reportType === SELECT_REPORT_TYPE ? label : {label},
- dropdownDisplay: label,
- }));
-
- return (
- onReportTypeChange(value as ReportViewType)}
- style={{ minWidth: 200 }}
- isInvalid={!selectedReportType && allSeries.length > 0}
- disabled={allSeries.length > 0}
- />
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
new file mode 100644
index 0000000000000..51ebe6c6bd9d5
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
@@ -0,0 +1,103 @@
+/*
+ * 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 { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { isEmpty } from 'lodash';
+import { RemoveSeries } from './remove_series';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+import { SeriesUrl } from '../../types';
+
+interface Props {
+ seriesId: string;
+ editorMode?: boolean;
+}
+export function SeriesActions({ seriesId, editorMode = false }: Props) {
+ const { getSeries, setSeries, allSeriesIds, removeSeries } = useSeriesStorage();
+ const series = getSeries(seriesId);
+
+ const onEdit = () => {
+ setSeries(seriesId, { ...series, isNew: true });
+ };
+
+ const copySeries = () => {
+ let copySeriesId: string = `${seriesId}-copy`;
+ if (allSeriesIds.includes(copySeriesId)) {
+ copySeriesId = copySeriesId + allSeriesIds.length;
+ }
+ setSeries(copySeriesId, series);
+ };
+
+ const { reportType, reportDefinitions, isNew, ...restSeries } = series;
+ const isSaveAble = reportType && !isEmpty(reportDefinitions);
+
+ const saveSeries = () => {
+ if (isSaveAble) {
+ const reportDefId = Object.values(reportDefinitions ?? {})[0];
+ let newSeriesId = `${reportDefId}-${reportType}`;
+
+ if (allSeriesIds.includes(newSeriesId)) {
+ newSeriesId = `${newSeriesId}-${allSeriesIds.length}`;
+ }
+ const newSeriesN: SeriesUrl = {
+ ...restSeries,
+ reportType,
+ reportDefinitions,
+ };
+
+ setSeries(newSeriesId, newSeriesN);
+ removeSeries(seriesId);
+ }
+ };
+
+ return (
+
+ {!editorMode && (
+
+
+
+ )}
+ {editorMode && (
+
+
+
+ )}
+ {editorMode && (
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx
new file mode 100644
index 0000000000000..02144c6929b38
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx
@@ -0,0 +1,155 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import React, { useState, Fragment } from 'react';
+import {
+ EuiButton,
+ EuiPopover,
+ EuiSpacer,
+ EuiButtonEmpty,
+ EuiFlexItem,
+ EuiFlexGroup,
+} from '@elastic/eui';
+import { FilterExpanded } from './filter_expanded';
+import { SeriesConfig } from '../../types';
+import { FieldLabels } from '../../configurations/constants/constants';
+import { SelectedFilters } from '../selected_filters';
+import { useSeriesStorage } from '../../hooks/use_series_storage';
+
+interface Props {
+ seriesId: string;
+ filterFields: SeriesConfig['filterFields'];
+ baseFilters: SeriesConfig['baseFilters'];
+ seriesConfig: SeriesConfig;
+ isNew?: boolean;
+ labels?: Record;
+}
+
+export interface Field {
+ label: string;
+ field: string;
+ nested?: string;
+ isNegated?: boolean;
+}
+
+export function SeriesFilter({
+ seriesConfig,
+ isNew,
+ seriesId,
+ filterFields = [],
+ baseFilters,
+ labels,
+}: Props) {
+ const [isPopoverVisible, setIsPopoverVisible] = useState(false);
+
+ const [selectedField, setSelectedField] = useState();
+
+ const options: Field[] = filterFields.map((field) => {
+ if (typeof field === 'string') {
+ return { label: labels?.[field] ?? FieldLabels[field], field };
+ }
+
+ return {
+ field: field.field,
+ nested: field.nested,
+ isNegated: field.isNegated,
+ label: labels?.[field.field] ?? FieldLabels[field.field],
+ };
+ });
+
+ const { setSeries, getSeries } = useSeriesStorage();
+ const urlSeries = getSeries(seriesId);
+
+ const button = (
+ {
+ setIsPopoverVisible((prevState) => !prevState);
+ }}
+ size="s"
+ >
+ {i18n.translate('xpack.observability.expView.seriesEditor.addFilter', {
+ defaultMessage: 'Add filter',
+ })}
+
+ );
+
+ const mainPanel = (
+ <>
+
+ {options.map((opt) => (
+
+ {
+ setSelectedField(opt);
+ }}
+ >
+ {opt.label}
+
+
+
+ ))}
+ >
+ );
+
+ const childPanel = selectedField ? (
+ {
+ setSelectedField(undefined);
+ }}
+ filters={baseFilters}
+ />
+ ) : null;
+
+ const closePopover = () => {
+ setIsPopoverVisible(false);
+ setSelectedField(undefined);
+ };
+
+ return (
+
+
+
+
+ {!selectedField ? mainPanel : childPanel}
+
+
+ {(urlSeries.filters ?? []).length > 0 && (
+
+ {
+ setSeries(seriesId, { ...urlSeries, filters: undefined });
+ }}
+ size="s"
+ >
+ {i18n.translate('xpack.observability.expView.seriesEditor.clearFilter', {
+ defaultMessage: 'Clear filters',
+ })}
+
+
+ )}
+
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx
deleted file mode 100644
index 801c885ec9a62..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx
+++ /dev/null
@@ -1,77 +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 { i18n } from '@kbn/i18n';
-
-import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui';
-import { SeriesConfig, SeriesUrl } from '../types';
-import { ReportDefinitionCol } from './columns/report_definition_col';
-import { OperationTypeSelect } from './columns/operation_type_select';
-import { parseCustomFieldName } from '../configurations/lens_attributes';
-import { SeriesFilter } from '../series_viewer/columns/series_filter';
-
-function getColumnType(seriesConfig: SeriesConfig, selectedMetricField?: string) {
- const { columnType } = parseCustomFieldName(seriesConfig, selectedMetricField);
-
- return columnType;
-}
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}
-export function ExpandedSeriesRow({ seriesId, series, seriesConfig }: Props) {
- if (!seriesConfig) {
- return null;
- }
-
- const { selectedMetricField } = series ?? {};
-
- const { hasOperationType, yAxisColumns } = seriesConfig;
-
- const columnType = getColumnType(seriesConfig, selectedMetricField);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {(hasOperationType || columnType === 'operation') && (
-
-
-
-
-
- )}
-
-
-
- );
-}
-
-const FILTERS_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.selectFilters', {
- defaultMessage: 'Filters',
-});
-
-const OPERATION_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.operation', {
- defaultMessage: 'Operation',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx
deleted file mode 100644
index 85eb85e0fc30a..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx
+++ /dev/null
@@ -1,101 +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 { EuiSuperSelect, EuiToolTip } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { useSeriesStorage } from '../hooks/use_series_storage';
-import { SeriesConfig, SeriesUrl } from '../types';
-import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
-import { RECORDS_FIELD, RECORDS_PERCENTAGE_FIELD } from '../configurations/constants';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- defaultValue?: string;
- metricOptions: SeriesConfig['metricOptions'];
-}
-
-const SELECT_REPORT_METRIC = 'SELECT_REPORT_METRIC';
-
-export function ReportMetricOptions({ seriesId, series, metricOptions }: Props) {
- const { setSeries } = useSeriesStorage();
-
- const { indexPatterns } = useAppIndexPatternContext();
-
- const onChange = (value: string) => {
- setSeries(seriesId, {
- ...series,
- selectedMetricField: value,
- });
- };
-
- if (!series.dataType) {
- return null;
- }
-
- const indexPattern = indexPatterns?.[series.dataType];
-
- const options = (metricOptions ?? []).map(({ label, field, id }) => {
- let disabled = false;
-
- if (field !== RECORDS_FIELD && field !== RECORDS_PERCENTAGE_FIELD && field) {
- disabled = !Boolean(indexPattern?.getFieldByName(field));
- }
- return {
- disabled,
- value: field || id,
- dropdownDisplay: disabled ? (
- {field},
- }}
- />
- }
- >
- {label}
-
- ) : (
- label
- ),
- inputDisplay: label,
- };
- });
-
- return (
- onChange(value)}
- style={{ minWidth: 220 }}
- />
- );
-}
-
-const SELECT_REPORT_METRIC_LABEL = i18n.translate(
- 'xpack.observability.expView.seriesEditor.selectReportMetric',
- {
- defaultMessage: 'Select report metric',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
similarity index 71%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.test.tsx
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
index 8fc5ae95fd41b..eb76772a66c7e 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
-import { mockAppIndexPattern, mockIndexPattern, mockUxSeries, render } from '../rtl_helpers';
+import { mockAppIndexPattern, mockIndexPattern, render } from '../rtl_helpers';
import { SelectedFilters } from './selected_filters';
import { getDefaultConfigs } from '../configurations/default_configs';
import { USER_AGENT_NAME } from '../configurations/constants/elasticsearch_fieldnames';
@@ -22,19 +22,11 @@ describe('SelectedFilters', function () {
});
it('should render properly', async function () {
- const filters = [{ field: USER_AGENT_NAME, values: ['Chrome'] }];
- const initSeries = { filters };
+ const initSeries = { filters: [{ field: USER_AGENT_NAME, values: ['Chrome'] }] };
- render(
- ,
- {
- initSeries,
- }
- );
+ render(, {
+ initSeries,
+ });
await waitFor(() => {
screen.getByText('Chrome');
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx
new file mode 100644
index 0000000000000..5d2ce6ba84951
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.tsx
@@ -0,0 +1,101 @@
+/*
+ * 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, { Fragment } from 'react';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { useSeriesStorage } from '../hooks/use_series_storage';
+import { FilterLabel } from '../components/filter_label';
+import { SeriesConfig, UrlFilter } from '../types';
+import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
+import { useSeriesFilters } from '../hooks/use_series_filters';
+import { getFiltersFromDefs } from '../hooks/use_lens_attributes';
+
+interface Props {
+ seriesId: string;
+ seriesConfig: SeriesConfig;
+ isNew?: boolean;
+}
+export function SelectedFilters({ seriesId, isNew, seriesConfig }: Props) {
+ const { getSeries } = useSeriesStorage();
+
+ const series = getSeries(seriesId);
+
+ const { reportDefinitions = {} } = series;
+
+ const { labels } = seriesConfig;
+
+ const filters: UrlFilter[] = series.filters ?? [];
+
+ let definitionFilters: UrlFilter[] = getFiltersFromDefs(reportDefinitions);
+
+ // we don't want to display report definition filters in new series view
+ if (isNew) {
+ definitionFilters = [];
+ }
+
+ const { removeFilter } = useSeriesFilters({ seriesId });
+
+ const { indexPattern } = useAppIndexPatternContext(series.dataType);
+
+ return (filters.length > 0 || definitionFilters.length > 0) && indexPattern ? (
+
+
+ {filters.map(({ field, values, notValues }) => (
+
+ {(values ?? []).map((val) => (
+
+ removeFilter({ field, value: val, negate: false })}
+ negate={false}
+ indexPattern={indexPattern}
+ />
+
+ ))}
+ {(notValues ?? []).map((val) => (
+
+ removeFilter({ field, value: val, negate: true })}
+ indexPattern={indexPattern}
+ />
+
+ ))}
+
+ ))}
+
+ {definitionFilters.map(({ field, values }) => (
+
+ {(values ?? []).map((val) => (
+
+ {
+ // FIXME handle this use case
+ }}
+ negate={false}
+ definitionFilter={true}
+ indexPattern={indexPattern}
+ />
+
+ ))}
+
+ ))}
+
+
+ ) : null;
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
index 80fe400830832..c3cc8484d1751 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
@@ -5,399 +5,134 @@
* 2.0.
*/
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { i18n } from '@kbn/i18n';
-import {
- EuiBasicTable,
- EuiButtonIcon,
- EuiSpacer,
- EuiFormRow,
- EuiFlexItem,
- EuiFlexGroup,
- EuiButtonEmpty,
-} from '@elastic/eui';
-import { rgba } from 'polished';
-import classNames from 'classnames';
-import { isEmpty } from 'lodash';
-import { euiStyled } from './../../../../../../../../src/plugins/kibana_react/common';
-import { AppDataType, SeriesConfig, ReportViewType, SeriesUrl } from '../types';
-import { SeriesContextValue, useSeriesStorage } from '../hooks/use_series_storage';
-import { IndexPatternState, useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
+import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { SeriesFilter } from './columns/series_filter';
+import { SeriesConfig } from '../types';
+import { NEW_SERIES_KEY, useSeriesStorage } from '../hooks/use_series_storage';
import { getDefaultConfigs } from '../configurations/default_configs';
-import { SeriesActions } from '../series_viewer/columns/series_actions';
-import { SeriesInfo } from '../series_viewer/columns/series_info';
-import { DataTypesSelect } from './columns/data_type_select';
import { DatePickerCol } from './columns/date_picker_col';
-import { ExpandedSeriesRow } from './expanded_series_row';
-import { SeriesName } from '../series_viewer/columns/series_name';
-import { ReportTypesSelect } from './columns/report_type_select';
-import { ViewActions } from '../views/view_actions';
-import { ReportMetricOptions } from './report_metric_options';
-import { Breakdowns } from '../series_viewer/columns/breakdowns';
+import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
+import { SeriesActions } from './columns/series_actions';
+import { ChartEditOptions } from './chart_edit_options';
-export interface ReportTypeItem {
- id: string;
- reportType: ReportViewType;
- label: string;
-}
-
-export interface BuilderItem {
- id: number;
- series: SeriesUrl;
+interface EditItem {
seriesConfig: SeriesConfig;
+ id: string;
}
-type ExpandedRowMap = Record;
-
-export const getSeriesToEdit = ({
- indexPatterns,
- allSeries,
- reportType,
-}: {
- allSeries: SeriesContextValue['allSeries'];
- indexPatterns: IndexPatternState;
- reportType: ReportViewType;
-}): BuilderItem[] => {
- const getDataViewSeries = (dataType: AppDataType) => {
- if (indexPatterns?.[dataType]) {
- return getDefaultConfigs({
- dataType,
- reportType,
- indexPattern: indexPatterns[dataType],
- });
- }
- };
-
- return allSeries.map((series, seriesIndex) => {
- const seriesConfig = getDataViewSeries(series.dataType)!;
-
- return { id: seriesIndex, series, seriesConfig };
- });
-};
-
-export const SeriesEditor = React.memo(function () {
- const [editorItems, setEditorItems] = useState([]);
-
- const { getSeries, allSeries, reportType, removeSeries } = useSeriesStorage();
-
- const { loading, indexPatterns } = useAppIndexPatternContext();
-
- const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>(
- {}
- );
-
- useEffect(() => {
- const newExpandRows: ExpandedRowMap = {};
-
- setEditorItems((prevState) => {
- const newEditorItems = getSeriesToEdit({
- reportType,
- allSeries,
- indexPatterns,
- });
-
- newEditorItems.forEach(({ series, id, seriesConfig }) => {
- const prevSeriesItem = prevState.find(({ id: prevId }) => prevId === id);
- if (
- prevSeriesItem &&
- series.selectedMetricField &&
- prevSeriesItem.series.selectedMetricField !== series.selectedMetricField
- ) {
- newExpandRows[id] = (
-
- );
- }
- });
- return [...newEditorItems];
- });
-
- setItemIdToExpandedRowMap((prevState) => {
- return { ...prevState, ...newExpandRows };
- });
- }, [allSeries, getSeries, indexPatterns, loading, reportType]);
-
- useEffect(() => {
- setItemIdToExpandedRowMap((prevState) => {
- const itemIdToExpandedRowMapValues = { ...prevState };
-
- const newEditorItems = getSeriesToEdit({
- reportType,
- allSeries,
- indexPatterns,
- });
-
- newEditorItems.forEach((item) => {
- if (itemIdToExpandedRowMapValues[item.id]) {
- itemIdToExpandedRowMapValues[item.id] = (
-
- );
- }
- });
- return itemIdToExpandedRowMapValues;
- });
- }, [allSeries, editorItems, indexPatterns, reportType]);
-
- const toggleDetails = (item: BuilderItem) => {
- const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
- if (itemIdToExpandedRowMapValues[item.id]) {
- delete itemIdToExpandedRowMapValues[item.id];
- } else {
- itemIdToExpandedRowMapValues[item.id] = (
-
- );
- }
- setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
- };
+export function SeriesEditor() {
+ const { allSeries, allSeriesIds } = useSeriesStorage();
const columns = [
- {
- align: 'left' as const,
- width: '40px',
- isExpander: true,
- field: 'id',
- name: '',
- render: (id: number, item: BuilderItem) =>
- item.series.dataType && item.series.selectedMetricField ? (
- toggleDetails(item)}
- isDisabled={!item.series.dataType || !item.series.selectedMetricField}
- aria-label={itemIdToExpandedRowMap[item.id] ? COLLAPSE_LABEL : EXPAND_LABEL}
- iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
- />
- ) : null,
- },
- {
- name: '',
- field: 'id',
- width: '40px',
- render: (seriesId: number, { seriesConfig, series }: BuilderItem) => (
-
- ),
- },
{
name: i18n.translate('xpack.observability.expView.seriesEditor.name', {
defaultMessage: 'Name',
}),
field: 'id',
- width: '20%',
- render: (seriesId: number, { series }: BuilderItem) => (
-
- ),
- },
- {
- name: i18n.translate('xpack.observability.expView.seriesEditor.dataType', {
- defaultMessage: 'Data type',
- }),
- field: 'id',
width: '15%',
- render: (seriesId: number, { series }: BuilderItem) => (
-
+ render: (seriesId: string) => (
+
+ {' '}
+ {seriesId === NEW_SERIES_KEY ? 'series-preview' : seriesId}
+
),
},
{
- name: i18n.translate('xpack.observability.expView.seriesEditor.reportMetric', {
- defaultMessage: 'Report metric',
+ name: i18n.translate('xpack.observability.expView.seriesEditor.filters', {
+ defaultMessage: 'Filters',
}),
- field: 'id',
+ field: 'defaultFilters',
width: '15%',
- render: (seriesId: number, { seriesConfig, series }: BuilderItem) => (
- (
+
),
},
{
- name: i18n.translate('xpack.observability.expView.seriesEditor.time', {
- defaultMessage: 'Time',
+ name: i18n.translate('xpack.observability.expView.seriesEditor.breakdowns', {
+ defaultMessage: 'Breakdowns',
}),
field: 'id',
- width: '27%',
- render: (seriesId: number, { series }: BuilderItem) => (
-
+ width: '25%',
+ render: (seriesId: string, { seriesConfig, id }: EditItem) => (
+
),
},
-
{
- name: i18n.translate('xpack.observability.expView.seriesBuilder.breakdownBy', {
- defaultMessage: 'Breakdown by',
- }),
- width: '10%',
- field: 'id',
- render: (seriesId: number, { series, seriesConfig }: BuilderItem) => (
-
+ name: (
+
+
+
),
+ width: '20%',
+ field: 'id',
+ align: 'right' as const,
+ render: (seriesId: string, item: EditItem) => ,
},
-
{
- name: i18n.translate('xpack.observability.expView.seriesBuilder.actions', {
+ name: i18n.translate('xpack.observability.expView.seriesEditor.actions', {
defaultMessage: 'Actions',
}),
align: 'center' as const,
- width: '8%',
+ width: '10%',
field: 'id',
- render: (seriesId: number, { series, seriesConfig }: BuilderItem) => (
-
- ),
+ render: (seriesId: string, item: EditItem) => ,
},
];
- const getRowProps = (item: BuilderItem) => {
- const { dataType, reportDefinitions, selectedMetricField } = item.series;
-
- return {
- className: classNames({
- isExpanded: itemIdToExpandedRowMap[item.id],
- isIncomplete: !dataType || isEmpty(reportDefinitions) || !selectedMetricField,
- }),
- // commenting this for now, since adding on click on row, blocks adding space
- // into text field for name column
- // ...(dataType && selectedMetricField
- // ? {
- // onClick: (evt: MouseEvent) => {
- // const targetElem = evt.target as HTMLElement;
- //
- // if (
- // targetElem.classList.contains('euiTableCellContent') &&
- // targetElem.tagName !== 'BUTTON'
- // ) {
- // toggleDetails(item);
- // }
- // evt.stopPropagation();
- // evt.preventDefault();
- // },
- // }
- // : {}),
- };
- };
-
- const resetView = () => {
- const totalSeries = allSeries.length;
- for (let i = totalSeries; i >= 0; i--) {
- removeSeries(i);
- }
- setEditorItems([]);
- setItemIdToExpandedRowMap({});
- };
-
- return (
-
-
-
-
-
-
-
-
-
- {reportType && (
-
- resetView()} color="text">
- {RESET_LABEL}
-
-
- )}
-
-
-
-
-
-
-
- {editorItems.length > 0 && (
-
- )}
-
-
-
- );
-});
-
-const Wrapper = euiStyled.div`
- max-height: 50vh;
- &::-webkit-scrollbar {
- height: ${({ theme }) => theme.eui.euiScrollBar};
- width: ${({ theme }) => theme.eui.euiScrollBar};
- }
- &::-webkit-scrollbar-thumb {
- background-clip: content-box;
- background-color: ${({ theme }) => rgba(theme.eui.euiColorDarkShade, 0.5)};
- border: ${({ theme }) => theme.eui.euiScrollBarCorner} solid transparent;
- }
- &::-webkit-scrollbar-corner,
- &::-webkit-scrollbar-track {
- background-color: transparent;
- }
-
- &&& {
- .euiTableRow-isExpandedRow .euiTableRowCell {
- border-top: none;
- background-color: #FFFFFF;
- border-bottom: 2px solid #d3dae6;
- border-right: 2px solid rgb(211, 218, 230);
- border-left: 2px solid rgb(211, 218, 230);
- }
-
- .isExpanded {
- border-right: 2px solid rgb(211, 218, 230);
- border-left: 2px solid rgb(211, 218, 230);
- .euiTableRowCell {
- border-bottom: none;
- }
- }
- .isIncomplete .euiTableRowCell {
- background-color: rgba(254, 197, 20, 0.1);
+ const { indexPatterns } = useAppIndexPatternContext();
+ const items: EditItem[] = [];
+
+ allSeriesIds.forEach((seriesKey) => {
+ const series = allSeries[seriesKey];
+ if (series?.reportType && indexPatterns[series.dataType] && !series.isNew) {
+ items.push({
+ id: seriesKey,
+ seriesConfig: getDefaultConfigs({
+ indexPattern: indexPatterns[series.dataType],
+ reportType: series.reportType,
+ dataType: series.dataType,
+ }),
+ });
}
- }
-`;
-
-export const LOADING_VIEW = i18n.translate(
- 'xpack.observability.expView.seriesBuilder.loadingView',
- {
- defaultMessage: 'Loading view ...',
- }
-);
-
-export const SELECT_REPORT_TYPE = i18n.translate(
- 'xpack.observability.expView.seriesBuilder.selectReportType',
- {
- defaultMessage: 'No report type selected',
- }
-);
-
-export const RESET_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.reset', {
- defaultMessage: 'Reset',
-});
+ });
-export const REPORT_TYPE_LABEL = i18n.translate(
- 'xpack.observability.expView.seriesBuilder.reportType',
- {
- defaultMessage: 'Report type',
+ if (items.length === 0 && allSeriesIds.length > 0) {
+ return null;
}
-);
-
-const COLLAPSE_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.collapse', {
- defaultMessage: 'Collapse',
-});
-const EXPAND_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.expand', {
- defaultMessage: 'Exapnd',
-});
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/chart_types.tsx
deleted file mode 100644
index e6ba505c82091..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/chart_types.tsx
+++ /dev/null
@@ -1,70 +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 { EuiPopover, EuiToolTip } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import {
- useKibana,
- ToolbarButton,
-} from '../../../../../../../../../src/plugins/kibana_react/public';
-import { ObservabilityPublicPluginsStart } from '../../../../../plugin';
-import { SeriesUrl, useFetcher } from '../../../../..';
-import { SeriesConfig } from '../../types';
-import { SeriesChartTypesSelect } from '../../series_editor/columns/chart_types';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}
-
-export function SeriesChartTypes({ seriesId, series, seriesConfig }: Props) {
- const seriesType = series?.seriesType ?? seriesConfig.defaultSeriesType;
-
- const {
- services: { lens },
- } = useKibana();
-
- const { data = [] } = useFetcher(() => lens.getXyVisTypes(), [lens]);
-
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
-
- return (
- setIsPopoverOpen(false)}
- button={
-
- id === seriesType)?.icon!}
- aria-label={CHART_TYPE_LABEL}
- onClick={() => setIsPopoverOpen((prevState) => !prevState)}
- />
-
- }
- >
-
-
- );
-}
-
-const EDIT_CHART_TYPE_LABEL = i18n.translate(
- 'xpack.observability.expView.seriesEditor.editChartSeriesLabel',
- {
- defaultMessage: 'Edit chart type for series',
- }
-);
-
-const CHART_TYPE_LABEL = i18n.translate('xpack.observability.expView.chartTypes.label', {
- defaultMessage: 'Chart type',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/remove_series.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/remove_series.tsx
deleted file mode 100644
index 2d38b81e12c9f..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/remove_series.tsx
+++ /dev/null
@@ -1,51 +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 { i18n } from '@kbn/i18n';
-import React from 'react';
-import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-
-interface Props {
- seriesId: number;
-}
-
-export function RemoveSeries({ seriesId }: Props) {
- const { removeSeries, allSeries } = useSeriesStorage();
-
- const onClick = () => {
- removeSeries(seriesId);
- };
-
- const isDisabled = seriesId === 0 && allSeries.length > 1;
-
- return (
-
-
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_actions.tsx
deleted file mode 100644
index 72ae111f002b1..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_actions.tsx
+++ /dev/null
@@ -1,104 +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 { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { RemoveSeries } from './remove_series';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { SeriesConfig, SeriesUrl } from '../../types';
-import { useDiscoverLink } from '../../hooks/use_discover_link';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}
-export function SeriesActions({ seriesId, series, seriesConfig }: Props) {
- const { setSeries, allSeries } = useSeriesStorage();
-
- const { href: discoverHref } = useDiscoverLink({ series, seriesConfig });
-
- const copySeries = () => {
- let copySeriesId: string = `${series.name}-copy`;
- if (allSeries.find(({ name }) => name === copySeriesId)) {
- copySeriesId = copySeriesId + allSeries.length;
- }
- setSeries(allSeries.length, { ...series, name: copySeriesId });
- };
-
- const toggleSeries = () => {
- if (series.hidden) {
- setSeries(seriesId, { ...series, hidden: undefined });
- } else {
- setSeries(seriesId, { ...series, hidden: true });
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_filter.tsx
deleted file mode 100644
index 87c17d03282c3..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_filter.tsx
+++ /dev/null
@@ -1,69 +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 { EuiFilterGroup, EuiSpacer } from '@elastic/eui';
-import { useRouteMatch } from 'react-router-dom';
-import { FilterExpanded } from './filter_expanded';
-import { SeriesConfig, SeriesUrl } from '../../types';
-import { FieldLabels } from '../../configurations/constants/constants';
-import { SelectedFilters } from '../selected_filters';
-
-interface Props {
- seriesId: number;
- seriesConfig: SeriesConfig;
- series: SeriesUrl;
-}
-
-export interface Field {
- label: string;
- field: string;
- nested?: string;
- isNegated?: boolean;
-}
-
-export function SeriesFilter({ series, seriesConfig, seriesId }: Props) {
- const isPreview = !!useRouteMatch('/exploratory-view/preview');
-
- const options: Field[] = seriesConfig.filterFields.map((field) => {
- if (typeof field === 'string') {
- return { label: seriesConfig.labels?.[field] ?? FieldLabels[field], field };
- }
-
- return {
- field: field.field,
- nested: field.nested,
- isNegated: field.isNegated,
- label: seriesConfig.labels?.[field.field] ?? FieldLabels[field.field],
- };
- });
-
- return (
- <>
- {!isPreview && (
- <>
-
- {options.map((opt) => (
-
- ))}
-
-
- >
- )}
-
- >
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_info.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_info.tsx
deleted file mode 100644
index 3506acbeb528d..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_info.tsx
+++ /dev/null
@@ -1,95 +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 { isEmpty } from 'lodash';
-import { EuiBadge, EuiBadgeGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { useRouteMatch } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-import { SeriesChartTypes } from './chart_types';
-import { SeriesConfig, SeriesUrl } from '../../types';
-import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
-import { SeriesColorPicker } from '../../components/series_color_picker';
-import { dataTypes } from '../../series_editor/columns/data_type_select';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig?: SeriesConfig;
-}
-
-export function SeriesInfo({ seriesId, series, seriesConfig }: Props) {
- const isConfigure = !!useRouteMatch('/exploratory-view/configure');
-
- const { dataType, reportDefinitions, selectedMetricField } = series;
-
- const { loading } = useAppIndexPatternContext();
-
- const isIncomplete =
- (!dataType || isEmpty(reportDefinitions) || !selectedMetricField) && !loading;
-
- if (!seriesConfig) {
- return null;
- }
-
- const { definitionFields, labels } = seriesConfig;
-
- const incompleteDefinition = isEmpty(reportDefinitions)
- ? i18n.translate('xpack.observability.overview.exploratoryView.missingReportDefinition', {
- defaultMessage: 'Missing {reportDefinition}',
- values: { reportDefinition: labels?.[definitionFields[0]] },
- })
- : '';
-
- let incompleteMessage = !selectedMetricField ? MISSING_REPORT_METRIC_LABEL : incompleteDefinition;
-
- if (!dataType) {
- incompleteMessage = MISSING_DATA_TYPE_LABEL;
- }
-
- if (!isIncomplete && seriesConfig && isConfigure) {
- return (
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
- {isIncomplete && {incompleteMessage}}
-
- {!isConfigure && (
-
-
- {dataTypes.find(({ id }) => id === dataType)!.label}
-
-
- )}
-
- );
-}
-
-const MISSING_REPORT_METRIC_LABEL = i18n.translate(
- 'xpack.observability.overview.exploratoryView.missingReportMetric',
- {
- defaultMessage: 'Missing report metric',
- }
-);
-
-const MISSING_DATA_TYPE_LABEL = i18n.translate(
- 'xpack.observability.overview.exploratoryView.missingDataType',
- {
- defaultMessage: 'Missing data type',
- }
-);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_name.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_name.tsx
deleted file mode 100644
index e35966a9fb0d2..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/series_name.tsx
+++ /dev/null
@@ -1,38 +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, ChangeEvent, useEffect } from 'react';
-import { EuiFieldText } from '@elastic/eui';
-import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { SeriesUrl } from '../../types';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
-}
-
-export function SeriesName({ series, seriesId }: Props) {
- const { setSeries } = useSeriesStorage();
-
- const [value, setValue] = useState(series.name);
-
- const onChange = (e: ChangeEvent) => {
- setValue(e.target.value);
- };
-
- const onSave = () => {
- if (value !== series.name) {
- setSeries(seriesId, { ...series, name: value });
- }
- };
-
- useEffect(() => {
- setValue(series.name);
- }, [series.name]);
-
- return ;
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/utils.ts
deleted file mode 100644
index b9ee53a7e8e2d..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/columns/utils.ts
+++ /dev/null
@@ -1,104 +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 moment from 'moment';
-import dateMath from '@elastic/datemath';
-import _isString from 'lodash/isString';
-
-const LAST = 'Last';
-const NEXT = 'Next';
-
-const isNow = (value: string) => value === 'now';
-
-export const isString = (value: any): value is string => _isString(value);
-export interface QuickSelect {
- timeTense: string;
- timeValue: number;
- timeUnits: TimeUnitId;
-}
-export type TimeUnitFromNowId = 's+' | 'm+' | 'h+' | 'd+' | 'w+' | 'M+' | 'y+';
-export type TimeUnitId = 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y';
-
-export interface RelativeOption {
- text: string;
- value: TimeUnitId | TimeUnitFromNowId;
-}
-
-export const relativeOptions: RelativeOption[] = [
- { text: 'Seconds ago', value: 's' },
- { text: 'Minutes ago', value: 'm' },
- { text: 'Hours ago', value: 'h' },
- { text: 'Days ago', value: 'd' },
- { text: 'Weeks ago', value: 'w' },
- { text: 'Months ago', value: 'M' },
- { text: 'Years ago', value: 'y' },
-
- { text: 'Seconds from now', value: 's+' },
- { text: 'Minutes from now', value: 'm+' },
- { text: 'Hours from now', value: 'h+' },
- { text: 'Days from now', value: 'd+' },
- { text: 'Weeks from now', value: 'w+' },
- { text: 'Months from now', value: 'M+' },
- { text: 'Years from now', value: 'y+' },
-];
-
-const timeUnitIds = relativeOptions
- .map(({ value }) => value)
- .filter((value) => !value.includes('+')) as TimeUnitId[];
-
-export const relativeUnitsFromLargestToSmallest = timeUnitIds.reverse();
-
-/**
- * This function returns time value, time unit and time tense for a given time string.
- *
- * For example: for `now-40m` it will parse output as time value to `40` time unit to `m` and time unit to `last`.
- *
- * If given a datetime string it will return a default value.
- *
- * If the given string is in the format such as `now/d` it will parse the string to moment object and find the time value, time unit and time tense using moment
- *
- * This function accepts two strings start and end time. I the start value is now then it uses the end value to parse.
- */
-export function parseTimeParts(start: string, end: string): QuickSelect | null {
- const value = isNow(start) ? end : start;
-
- const matches = isString(value) && value.match(/now(([-+])(\d+)([smhdwMy])(\/[smhdwMy])?)?/);
-
- if (!matches) {
- return null;
- }
-
- const operator = matches[2];
- const matchedTimeValue = matches[3];
- const timeUnits = matches[4] as TimeUnitId;
-
- if (matchedTimeValue && timeUnits && operator) {
- return {
- timeTense: operator === '+' ? NEXT : LAST,
- timeUnits,
- timeValue: parseInt(matchedTimeValue, 10),
- };
- }
-
- const duration = moment.duration(moment().diff(dateMath.parse(value)));
- let unitOp = '';
- for (let i = 0; i < relativeUnitsFromLargestToSmallest.length; i++) {
- const as = duration.as(relativeUnitsFromLargestToSmallest[i]);
- if (as < 0) {
- unitOp = '+';
- }
- if (Math.abs(as) > 1) {
- return {
- timeValue: Math.round(Math.abs(as)),
- timeUnits: relativeUnitsFromLargestToSmallest[i],
- timeTense: unitOp === '+' ? NEXT : LAST,
- };
- }
- }
-
- return null;
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.tsx
deleted file mode 100644
index 46adba1dbde55..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/selected_filters.tsx
+++ /dev/null
@@ -1,132 +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, { Fragment } from 'react';
-import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { useRouteMatch } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-import { FilterLabel } from '../components/filter_label';
-import { SeriesConfig, SeriesUrl, UrlFilter } from '../types';
-import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
-import { useSeriesFilters } from '../hooks/use_series_filters';
-import { getFiltersFromDefs } from '../hooks/use_lens_attributes';
-import { useSeriesStorage } from '../hooks/use_series_storage';
-
-interface Props {
- seriesId: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}
-export function SelectedFilters({ seriesId, series, seriesConfig }: Props) {
- const { setSeries } = useSeriesStorage();
-
- const isPreview = !!useRouteMatch('/exploratory-view/preview');
-
- const { reportDefinitions = {} } = series;
-
- const { labels } = seriesConfig;
-
- const filters: UrlFilter[] = series.filters ?? [];
-
- let definitionFilters: UrlFilter[] = getFiltersFromDefs(reportDefinitions);
-
- const isConfigure = !!useRouteMatch('/exploratory-view/configure');
-
- // we don't want to display report definition filters in new series view
- if (isConfigure) {
- definitionFilters = [];
- }
-
- const { removeFilter } = useSeriesFilters({ seriesId, series });
-
- const { indexPattern } = useAppIndexPatternContext(series.dataType);
-
- if ((filters.length === 0 && definitionFilters.length === 0) || !indexPattern) {
- return null;
- }
-
- return (
-
- {filters.map(({ field, values, notValues }) => (
-
- {(values ?? []).length > 0 && (
-
- {
- values?.forEach((val) => {
- removeFilter({ field, value: val, negate: false });
- });
- }}
- negate={false}
- indexPattern={indexPattern}
- />
-
- )}
- {(notValues ?? []).length > 0 && (
-
- {
- values?.forEach((val) => {
- removeFilter({ field, value: val, negate: false });
- });
- }}
- indexPattern={indexPattern}
- />
-
- )}
-
- ))}
-
- {definitionFilters.map(({ field, values }) =>
- values ? (
-
- {}}
- negate={false}
- definitionFilter={true}
- indexPattern={indexPattern}
- />
-
- ) : null
- )}
-
- {(series.filters ?? []).length > 0 && !isPreview && (
-
- {
- setSeries(seriesId, { ...series, filters: undefined });
- }}
- size="s"
- >
- {i18n.translate('xpack.observability.expView.seriesEditor.clearFilter', {
- defaultMessage: 'Clear filters',
- })}
-
-
- )}
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/series_viewer.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/series_viewer.tsx
deleted file mode 100644
index 85d65dcac6ac3..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_viewer/series_viewer.tsx
+++ /dev/null
@@ -1,120 +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 { i18n } from '@kbn/i18n';
-import { isEmpty } from 'lodash';
-import { EuiBasicTable, EuiSpacer, EuiText } from '@elastic/eui';
-import { SeriesFilter } from './columns/series_filter';
-import { SeriesConfig, SeriesUrl } from '../types';
-import { useSeriesStorage } from '../hooks/use_series_storage';
-import { getDefaultConfigs } from '../configurations/default_configs';
-import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
-import { SeriesInfo } from './columns/series_info';
-import { SeriesDatePicker } from '../components/series_date_picker';
-import { NO_BREAK_DOWN_LABEL } from './columns/breakdowns';
-
-interface EditItem {
- id: number;
- series: SeriesUrl;
- seriesConfig: SeriesConfig;
-}
-
-export function SeriesViewer() {
- const { allSeries, reportType } = useSeriesStorage();
-
- const columns = [
- {
- name: '',
- field: 'id',
- width: '10%',
- render: (seriesId: number, { seriesConfig, series }: EditItem) => (
-
- ),
- },
- {
- name: i18n.translate('xpack.observability.expView.seriesEditor.name', {
- defaultMessage: 'Name',
- }),
- field: 'id',
- width: '15%',
- render: (seriesId: number, { series }: EditItem) => {series.name},
- },
- {
- name: i18n.translate('xpack.observability.expView.seriesEditor.filters', {
- defaultMessage: 'Filters',
- }),
- field: 'id',
- width: '35%',
- render: (seriesId: number, { series, seriesConfig }: EditItem) => (
-
- ),
- },
- {
- name: i18n.translate('xpack.observability.expView.seriesEditor.breakdownBy', {
- defaultMessage: 'Breakdown by',
- }),
- field: 'seriesId',
- width: '10%',
- render: (seriesId: number, { seriesConfig: { labels }, series }: EditItem) => (
- {series.breakdown ? labels[series.breakdown] : NO_BREAK_DOWN_LABEL}
- ),
- },
- {
- name: i18n.translate('xpack.observability.expView.seriesEditor.time', {
- defaultMessage: 'Time',
- }),
- width: '30%',
- field: 'id',
- render: (seriesId: number, { series }: EditItem) => (
-
- ),
- },
- ];
-
- const { indexPatterns } = useAppIndexPatternContext();
- const items: EditItem[] = [];
-
- allSeries.forEach((series, seriesIndex) => {
- if (indexPatterns[series.dataType] && !isEmpty(series.reportDefinitions)) {
- items.push({
- series,
- id: seriesIndex,
- seriesConfig: getDefaultConfigs({
- reportType,
- dataType: series.dataType,
- indexPattern: indexPatterns[series.dataType],
- }),
- });
- }
- });
-
- if (items.length === 0 && allSeries.length > 0) {
- return null;
- }
-
- return (
- <>
-
-
-
- >
- );
-}
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 4bba0c221f3c5..fbda2f4ff62e2 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
@@ -6,7 +6,7 @@
*/
import { PaletteOutput } from 'src/plugins/charts/public';
-import { ExistsFilter, PhraseFilter } from '@kbn/es-query';
+import { ExistsFilter } from '@kbn/es-query';
import {
LastValueIndexPatternColumn,
DateHistogramIndexPatternColumn,
@@ -42,7 +42,7 @@ export interface MetricOption {
field?: string;
label: string;
description?: string;
- columnType?: 'range' | 'operation' | 'FILTER_RECORDS' | 'TERMS_COLUMN' | 'unique_count';
+ columnType?: 'range' | 'operation' | 'FILTER_RECORDS' | 'TERMS_COLUMN';
columnFilters?: ColumnFilter[];
timeScale?: string;
}
@@ -55,7 +55,7 @@ export interface SeriesConfig {
defaultSeriesType: SeriesType;
filterFields: Array;
seriesTypes: SeriesType[];
- baseFilters?: Array;
+ baseFilters?: PersistableFilter[] | ExistsFilter[];
definitionFields: string[];
metricOptions?: MetricOption[];
labels: Record;
@@ -69,7 +69,6 @@ export interface SeriesConfig {
export type URLReportDefinition = Record;
export interface SeriesUrl {
- name: string;
time: {
to: string;
from: string;
@@ -77,12 +76,12 @@ export interface SeriesUrl {
breakdown?: string;
filters?: UrlFilter[];
seriesType?: SeriesType;
+ reportType: ReportViewType;
operationType?: OperationType;
dataType: AppDataType;
reportDefinitions?: URLReportDefinition;
selectedMetricField?: string;
- hidden?: boolean;
- color?: string;
+ isNew?: boolean;
}
export interface UrlFilter {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/series_views.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/views/series_views.tsx
deleted file mode 100644
index e0b46102caba0..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/series_views.tsx
+++ /dev/null
@@ -1,85 +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, { RefObject, useEffect, useState } from 'react';
-
-import { EuiTabs, EuiTab, EuiButtonIcon } from '@elastic/eui';
-import { useHistory, useParams } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-import { SeriesEditor } from '../series_editor/series_editor';
-import { SeriesViewer } from '../series_viewer/series_viewer';
-import { PanelId } from '../exploratory_view';
-
-const tabs = [
- {
- id: 'preview' as const,
- name: i18n.translate('xpack.observability.overview.exploratoryView.preview', {
- defaultMessage: 'Preview',
- }),
- },
- {
- id: 'configure' as const,
- name: i18n.translate('xpack.observability.overview.exploratoryView.configureSeries', {
- defaultMessage: 'Configure series',
- }),
- },
-];
-
-type ViewTab = 'preview' | 'configure';
-
-export function SeriesViews({
- seriesBuilderRef,
- onSeriesPanelCollapse,
-}: {
- seriesBuilderRef: RefObject;
- onSeriesPanelCollapse: (panel: PanelId) => void;
-}) {
- const params = useParams<{ mode: ViewTab }>();
-
- const history = useHistory();
-
- const [selectedTabId, setSelectedTabId] = useState('configure');
-
- const onSelectedTabChanged = (id: ViewTab) => {
- setSelectedTabId(id);
- history.push('/exploratory-view/' + id);
- };
-
- useEffect(() => {
- setSelectedTabId(params.mode);
- }, [params.mode]);
-
- const renderTabs = () => {
- return tabs.map((tab, index) => (
- onSelectedTabChanged(tab.id)}
- isSelected={tab.id === selectedTabId}
- key={index}
- >
- {tab.id === 'preview' && selectedTabId === 'preview' ? (
-
- onSeriesPanelCollapse('seriesPanel')}
- />
- {tab.name}
-
- ) : (
- tab.name
- )}
-
- ));
- };
-
- return (
-
- {renderTabs()}
- {selectedTabId === 'preview' && }
- {selectedTabId === 'configure' && }
-
- );
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx
deleted file mode 100644
index db1f23ad9b6e3..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/views/view_actions.tsx
+++ /dev/null
@@ -1,119 +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, { useEffect, useState } from 'react';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiToolTip } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { isEqual } from 'lodash';
-import {
- allSeriesKey,
- convertAllShortSeries,
- NEW_SERIES_KEY,
- useSeriesStorage,
-} from '../hooks/use_series_storage';
-import { SeriesUrl } from '../types';
-import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
-import { BuilderItem, getSeriesToEdit } from '../series_editor/series_editor';
-import { DEFAULT_TIME, ReportTypes } from '../configurations/constants';
-
-export function ViewActions() {
- const [editorItems, setEditorItems] = useState([]);
- const {
- getSeries,
- allSeries,
- setSeries,
- storage,
- reportType,
- autoApply,
- setAutoApply,
- applyChanges,
- } = useSeriesStorage();
-
- const { loading, indexPatterns } = useAppIndexPatternContext();
-
- useEffect(() => {
- setEditorItems(getSeriesToEdit({ allSeries, indexPatterns, reportType }));
- }, [allSeries, getSeries, indexPatterns, loading, reportType]);
-
- const addSeries = () => {
- const prevSeries = allSeries?.[0];
- const name = `${NEW_SERIES_KEY}-${editorItems.length + 1}`;
- const nextSeries = { name } as SeriesUrl;
-
- const nextSeriesId = allSeries.length;
-
- if (reportType === 'data-distribution') {
- setSeries(nextSeriesId, {
- ...nextSeries,
- time: prevSeries?.time || DEFAULT_TIME,
- } as SeriesUrl);
- } else {
- setSeries(
- nextSeriesId,
- prevSeries ? nextSeries : ({ ...nextSeries, time: DEFAULT_TIME } as SeriesUrl)
- );
- }
- };
-
- const noChanges = isEqual(allSeries, convertAllShortSeries(storage.get(allSeriesKey) ?? []));
-
- const isAddDisabled =
- !reportType ||
- ((reportType === ReportTypes.CORE_WEB_VITAL ||
- reportType === ReportTypes.DEVICE_DISTRIBUTION) &&
- allSeries.length > 0);
-
- return (
-
-
- setAutoApply(!autoApply)}
- compressed
- />
-
- {!autoApply && (
-
- applyChanges()} isDisabled={autoApply || noChanges} fill>
- {i18n.translate('xpack.observability.expView.seriesBuilder.apply', {
- defaultMessage: 'Apply changes',
- })}
-
-
- )}
-
-
- addSeries()} isDisabled={isAddDisabled}>
- {i18n.translate('xpack.observability.expView.seriesBuilder.addSeries', {
- defaultMessage: 'Add series',
- })}
-
-
-
-
- );
-}
-
-const AUTO_APPLY_LABEL = i18n.translate('xpack.observability.expView.seriesBuilder.autoApply', {
- defaultMessage: 'Auto apply',
-});
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
index 0735df53888aa..fc562fa80e26d 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
@@ -6,24 +6,15 @@
*/
import React, { useEffect, useState } from 'react';
-import { union, isEmpty } from 'lodash';
-import {
- EuiComboBox,
- EuiFormControlLayout,
- EuiComboBoxOptionOption,
- EuiFormRow,
-} from '@elastic/eui';
+import { union } from 'lodash';
+import { EuiComboBox, EuiFormControlLayout, EuiComboBoxOptionOption } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { FieldValueSelectionProps } from './types';
export const ALL_VALUES_SELECTED = 'ALL_VALUES';
const formatOptions = (values?: string[], allowAllValuesSelection?: boolean) => {
const uniqueValues = Array.from(
- new Set(
- allowAllValuesSelection && (values ?? []).length > 0
- ? ['ALL_VALUES', ...(values ?? [])]
- : values
- )
+ new Set(allowAllValuesSelection ? ['ALL_VALUES', ...(values ?? [])] : values)
);
return (uniqueValues ?? []).map((label) => ({
@@ -39,9 +30,7 @@ export function FieldValueCombobox({
loading,
values,
setQuery,
- usePrependLabel = true,
compressed = true,
- required = true,
allowAllValuesSelection,
onChange: onSelectionChange,
}: FieldValueSelectionProps) {
@@ -65,35 +54,29 @@ export function FieldValueCombobox({
onSelectionChange(selectedValuesN.map(({ label: lbl }) => lbl));
};
- const comboBox = (
- {
- setQuery(searchVal);
- }}
- options={options}
- selectedOptions={options.filter((opt) => selectedValue?.includes(opt.label))}
- onChange={onChange}
- isInvalid={required && isEmpty(selectedValue)}
- />
- );
-
- return usePrependLabel ? (
+ return (
- {comboBox}
+ {
+ setQuery(searchVal);
+ }}
+ options={options}
+ selectedOptions={options.filter((opt) => selectedValue?.includes(opt.label))}
+ onChange={onChange}
+ />
- ) : (
-
- {comboBox}
-
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx
index cee3ab8aea28b..f713af9768229 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx
@@ -70,7 +70,6 @@ export function FieldValueSelection({
values = [],
selectedValue,
excludedValue,
- allowExclusions = true,
compressed = true,
onChange: onSelectionChange,
}: FieldValueSelectionProps) {
@@ -174,8 +173,8 @@ export function FieldValueSelection({
}}
options={options}
onChange={onChange}
- allowExclusions={allowExclusions}
isLoading={loading && !query && options.length === 0}
+ allowExclusions={true}
>
{(list, search) => (
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.test.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.test.tsx
index 6671c43dd8c7b..556a8e7052347 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.test.tsx
@@ -95,7 +95,6 @@ describe('FieldValueSuggestions', () => {
selectedValue={[]}
filters={[]}
asCombobox={false}
- allowExclusions={true}
/>
);
@@ -120,7 +119,6 @@ describe('FieldValueSuggestions', () => {
excludedValue={['Pak']}
filters={[]}
asCombobox={false}
- allowExclusions={true}
/>
);
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx
index 65e1d0932e4ed..54114c7604644 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx
@@ -28,10 +28,7 @@ export function FieldValueSuggestions({
singleSelection,
compressed,
asFilterButton,
- usePrependLabel,
allowAllValuesSelection,
- required,
- allowExclusions = true,
asCombobox = true,
onChange: onSelectionChange,
}: FieldValueSuggestionsProps) {
@@ -67,10 +64,7 @@ export function FieldValueSuggestions({
width={width}
compressed={compressed}
asFilterButton={asFilterButton}
- usePrependLabel={usePrependLabel}
- allowExclusions={allowExclusions}
allowAllValuesSelection={allowAllValuesSelection}
- required={required}
/>
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
index 73b3d78ce8700..d857b39b074ac 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
@@ -23,10 +23,7 @@ interface CommonProps {
compressed?: boolean;
asFilterButton?: boolean;
showCount?: boolean;
- usePrependLabel?: boolean;
- allowExclusions?: boolean;
allowAllValuesSelection?: boolean;
- required?: boolean;
}
export type FieldValueSuggestionsProps = CommonProps & {
diff --git a/x-pack/plugins/observability/public/components/shared/filter_value_label/filter_value_label.tsx b/x-pack/plugins/observability/public/components/shared/filter_value_label/filter_value_label.tsx
index 9e7b96b02206f..01d727071770d 100644
--- a/x-pack/plugins/observability/public/components/shared/filter_value_label/filter_value_label.tsx
+++ b/x-pack/plugins/observability/public/components/shared/filter_value_label/filter_value_label.tsx
@@ -18,25 +18,21 @@ export function buildFilterLabel({
negate,
}: {
label: string;
- value: string | string[];
+ value: string;
negate: boolean;
field: string;
indexPattern: IndexPattern;
}) {
const indexField = indexPattern.getFieldByName(field)!;
- const filter =
- value instanceof Array && value.length > 1
- ? esFilters.buildPhrasesFilter(indexField, value, indexPattern)
- : esFilters.buildPhraseFilter(indexField, value as string, indexPattern);
+ const filter = esFilters.buildPhraseFilter(indexField, value, indexPattern);
- filter.meta.type = value instanceof Array && value.length > 1 ? 'phrases' : 'phrase';
-
- filter.meta.value = value as string;
+ filter.meta.value = value;
filter.meta.key = label;
filter.meta.alias = null;
filter.meta.negate = negate;
filter.meta.disabled = false;
+ filter.meta.type = 'phrase';
return filter;
}
@@ -44,10 +40,10 @@ export function buildFilterLabel({
interface Props {
field: string;
label: string;
- value: string | string[];
+ value: string;
negate: boolean;
- removeFilter: (field: string, value: string | string[], notVal: boolean) => void;
- invertFilter: (val: { field: string; value: string | string[]; negate: boolean }) => void;
+ removeFilter: (field: string, value: string, notVal: boolean) => void;
+ invertFilter: (val: { field: string; value: string; negate: boolean }) => void;
indexPattern: IndexPattern;
allowExclusion?: boolean;
}
diff --git a/x-pack/plugins/observability/public/components/shared/index.tsx b/x-pack/plugins/observability/public/components/shared/index.tsx
index afc053604fcdf..9d557a40b7987 100644
--- a/x-pack/plugins/observability/public/components/shared/index.tsx
+++ b/x-pack/plugins/observability/public/components/shared/index.tsx
@@ -6,7 +6,6 @@
*/
import React, { lazy, Suspense } from 'react';
-import { EuiLoadingSpinner } from '@elastic/eui';
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
@@ -27,7 +26,7 @@ const HeaderMenuPortalLazy = lazy(() => import('./header_menu_portal'));
export function HeaderMenuPortal(props: HeaderMenuPortalProps) {
return (
-
}>
+
);
diff --git a/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx
index 198b4092b0ed6..82a0fc39b8519 100644
--- a/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx
+++ b/x-pack/plugins/observability/public/hooks/use_quick_time_ranges.tsx
@@ -7,7 +7,7 @@
import { useUiSetting } from '../../../../../src/plugins/kibana_react/public';
import { UI_SETTINGS } from '../../../../../src/plugins/data/common';
-import { TimePickerQuickRange } from '../components/shared/exploratory_view/components/series_date_picker';
+import { TimePickerQuickRange } from '../components/shared/exploratory_view/series_date_picker';
export function useQuickTimeRanges() {
const timePickerQuickRanges = useUiSetting
(
diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts
index 334733e363495..71b83b9e05324 100644
--- a/x-pack/plugins/observability/public/plugin.ts
+++ b/x-pack/plugins/observability/public/plugin.ts
@@ -24,7 +24,6 @@ import type {
DataPublicPluginSetup,
DataPublicPluginStart,
} from '../../../../src/plugins/data/public';
-import type { DiscoverStart } from '../../../../src/plugins/discover/public';
import type {
HomePublicPluginSetup,
HomePublicPluginStart,
@@ -57,7 +56,6 @@ export interface ObservabilityPublicPluginsStart {
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
data: DataPublicPluginStart;
lens: LensPublicStart;
- discover: DiscoverStart;
}
export type ObservabilityPublicStart = ReturnType;
diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx
index 09d22496c98ff..f97e3fb996441 100644
--- a/x-pack/plugins/observability/public/routes/index.tsx
+++ b/x-pack/plugins/observability/public/routes/index.tsx
@@ -7,7 +7,6 @@
import * as t from 'io-ts';
import React from 'react';
-import { Redirect } from 'react-router-dom';
import { alertStatusRt } from '../../common/typings';
import { ExploratoryViewPage } from '../components/shared/exploratory_view';
import { AlertsPage } from '../pages/alerts';
@@ -100,20 +99,7 @@ export const routes = {
}),
},
},
- '/exploratory-view/': {
- handler: () => {
- return ;
- },
- params: {
- query: t.partial({
- rangeFrom: t.string,
- rangeTo: t.string,
- refreshPaused: jsonRt.pipe(t.boolean),
- refreshInterval: jsonRt.pipe(t.number),
- }),
- },
- },
- '/exploratory-view/:mode': {
+ '/exploratory-view': {
handler: () => {
return ;
},
@@ -126,4 +112,18 @@ export const routes = {
}),
},
},
+ // enable this to test multi series architecture
+ // '/exploratory-view/multi': {
+ // handler: () => {
+ // return ;
+ // },
+ // params: {
+ // query: t.partial({
+ // rangeFrom: t.string,
+ // rangeTo: t.string,
+ // refreshPaused: jsonRt.pipe(t.boolean),
+ // refreshInterval: jsonRt.pipe(t.number),
+ // }),
+ // },
+ // },
};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 0d4432d8dac56..c8e3526005963 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18514,10 +18514,20 @@
"xpack.observability.expView.operationType.99thPercentile": "99パーセンタイル",
"xpack.observability.expView.operationType.average": "平均",
"xpack.observability.expView.operationType.median": "中央",
+ "xpack.observability.expView.reportType.noDataType": "データ型を選択すると、系列の構築を開始します。",
+ "xpack.observability.expView.seriesBuilder.breakdown": "内訳",
+ "xpack.observability.expView.seriesBuilder.dataType": "データ型",
+ "xpack.observability.expView.seriesBuilder.definition": "定義",
+ "xpack.observability.expView.seriesBuilder.filters": "フィルター",
+ "xpack.observability.expView.seriesBuilder.report": "レポート",
"xpack.observability.expView.seriesBuilder.selectReportType": "レポートタイプを選択すると、ビジュアライゼーションを定義します。",
+ "xpack.observability.expView.seriesEditor.actions": "アクション",
+ "xpack.observability.expView.seriesEditor.addFilter": "フィルターを追加します",
+ "xpack.observability.expView.seriesEditor.breakdowns": "内訳",
"xpack.observability.expView.seriesEditor.clearFilter": "フィルターを消去",
"xpack.observability.expView.seriesEditor.filters": "フィルター",
"xpack.observability.expView.seriesEditor.name": "名前",
+ "xpack.observability.expView.seriesEditor.removeSeries": "クリックすると、系列を削除します",
"xpack.observability.expView.seriesEditor.time": "時間",
"xpack.observability.featureCatalogueDescription": "専用UIで、ログ、メトリック、アプリケーショントレース、システム可用性を連結します。",
"xpack.observability.featureCatalogueDescription1": "インフラストラクチャメトリックを監視します。",
@@ -18583,6 +18593,7 @@
"xpack.observability.overview.uptime.up": "アップ",
"xpack.observability.overview.ux.appLink": "アプリで表示",
"xpack.observability.overview.ux.title": "ユーザーエクスペリエンス",
+ "xpack.observability.reportTypeCol.nodata": "利用可能なデータがありません",
"xpack.observability.resources.documentation": "ドキュメント",
"xpack.observability.resources.forum": "ディスカッションフォーラム",
"xpack.observability.resources.title": "リソース",
@@ -18596,6 +18607,7 @@
"xpack.observability.section.apps.uptime.description": "サイトとサービスの可用性をアクティブに監視するアラートを受信し、問題をより迅速に解決して、ユーザーエクスペリエンスを最適化します。",
"xpack.observability.section.apps.uptime.title": "アップタイム",
"xpack.observability.section.errorPanel": "データの取得時にエラーが発生しました。再試行してください",
+ "xpack.observability.seriesEditor.edit": "系列を編集",
"xpack.observability.transactionRateLabel": "{value} tpm",
"xpack.observability.ux.coreVitals.average": "平均",
"xpack.observability.ux.coreVitals.averageMessage": " {bad}未満",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index fc8b27aa497cd..cf31ec8d8eceb 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18930,10 +18930,20 @@
"xpack.observability.expView.operationType.99thPercentile": "第 99 个百分位",
"xpack.observability.expView.operationType.average": "平均值",
"xpack.observability.expView.operationType.median": "中值",
+ "xpack.observability.expView.reportType.noDataType": "选择数据类型以开始构建序列。",
+ "xpack.observability.expView.seriesBuilder.breakdown": "分解",
+ "xpack.observability.expView.seriesBuilder.dataType": "数据类型",
+ "xpack.observability.expView.seriesBuilder.definition": "定义",
+ "xpack.observability.expView.seriesBuilder.filters": "筛选",
+ "xpack.observability.expView.seriesBuilder.report": "报告",
"xpack.observability.expView.seriesBuilder.selectReportType": "选择报告类型以定义可视化。",
+ "xpack.observability.expView.seriesEditor.actions": "操作",
+ "xpack.observability.expView.seriesEditor.addFilter": "添加筛选",
+ "xpack.observability.expView.seriesEditor.breakdowns": "分解",
"xpack.observability.expView.seriesEditor.clearFilter": "清除筛选",
"xpack.observability.expView.seriesEditor.filters": "筛选",
"xpack.observability.expView.seriesEditor.name": "名称",
+ "xpack.observability.expView.seriesEditor.removeSeries": "单击移除序列",
"xpack.observability.expView.seriesEditor.time": "时间",
"xpack.observability.featureCatalogueDescription": "通过专用 UI 整合您的日志、指标、应用程序跟踪和系统可用性。",
"xpack.observability.featureCatalogueDescription1": "监测基础架构指标。",
@@ -18999,6 +19009,7 @@
"xpack.observability.overview.uptime.up": "运行",
"xpack.observability.overview.ux.appLink": "在应用中查看",
"xpack.observability.overview.ux.title": "用户体验",
+ "xpack.observability.reportTypeCol.nodata": "没有可用数据",
"xpack.observability.resources.documentation": "文档",
"xpack.observability.resources.forum": "讨论论坛",
"xpack.observability.resources.title": "资源",
@@ -19012,6 +19023,7 @@
"xpack.observability.section.apps.uptime.description": "主动监测站点和服务的可用性。接收告警并更快地解决问题,从而优化用户体验。",
"xpack.observability.section.apps.uptime.title": "运行时间",
"xpack.observability.section.errorPanel": "尝试提取数据时发生错误。请重试",
+ "xpack.observability.seriesEditor.edit": "编辑序列",
"xpack.observability.transactionRateLabel": "{value} tpm",
"xpack.observability.ux.coreVitals.average": "平均值",
"xpack.observability.ux.coreVitals.averageMessage": " 且小于 {bad}",
diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
index aa981071b7ee2..1a53a2c9b64a0 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
+++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
@@ -22,7 +22,6 @@ import React, { useContext } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import numeral from '@elastic/numeral';
import moment from 'moment';
-import { useSelector } from 'react-redux';
import { getChartDateLabel } from '../../../lib/helper';
import { ChartWrapper } from './chart_wrapper';
import { UptimeThemeContext } from '../../../contexts';
@@ -33,7 +32,6 @@ import { getDateRangeFromChartElement } from './utils';
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../translations';
import { createExploratoryViewUrl } from '../../../../../observability/public';
import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context';
-import { monitorStatusSelector } from '../../../state/selectors';
export interface PingHistogramComponentProps {
/**
@@ -75,8 +73,6 @@ export const PingHistogramComponent: React.FC = ({
const monitorId = useMonitorId();
- const selectedMonitor = useSelector(monitorStatusSelector);
-
const { basePath } = useUptimeSettingsContext();
const [getUrlParams, updateUrlParams] = useUrlParams();
@@ -193,21 +189,12 @@ export const PingHistogramComponent: React.FC = ({
const pingHistogramExploratoryViewLink = createExploratoryViewUrl(
{
- reportType: 'kpi-over-time',
- allSeries: [
- {
- name: `${monitorId}-pings`,
- dataType: 'synthetics',
- selectedMetricField: 'summary.up',
- time: { from: dateRangeStart, to: dateRangeEnd },
- reportDefinitions: {
- 'monitor.name':
- monitorId && selectedMonitor?.monitor?.name
- ? [selectedMonitor.monitor.name]
- : ['ALL_VALUES'],
- },
- },
- ],
+ 'pings-over-time': {
+ dataType: 'synthetics',
+ reportType: 'kpi-over-time',
+ time: { from: dateRangeStart, to: dateRangeEnd },
+ ...(monitorId ? { filters: [{ field: 'monitor.id', values: [monitorId] }] } : {}),
+ },
},
basePath
);
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
index c459fe46da975..9f00dd2e8f061 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
@@ -10,15 +10,13 @@ import { EuiHeaderLinks, EuiToolTip, EuiHeaderLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useHistory } from 'react-router-dom';
-import { useSelector } from 'react-redux';
-import { createExploratoryViewUrl } from '../../../../../observability/public';
+import { createExploratoryViewUrl, SeriesUrl } from '../../../../../observability/public';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context';
import { useGetUrlParams } from '../../../hooks';
import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers';
import { SETTINGS_ROUTE } from '../../../../common/constants';
import { stringifyUrlParams } from '../../../lib/helper/stringify_url_params';
-import { monitorStatusSelector } from '../../../state/selectors';
const ADD_DATA_LABEL = i18n.translate('xpack.uptime.addDataButtonLabel', {
defaultMessage: 'Add data',
@@ -40,28 +38,13 @@ export function ActionMenuContent(): React.ReactElement {
const { dateRangeStart, dateRangeEnd } = params;
const history = useHistory();
- const selectedMonitor = useSelector(monitorStatusSelector);
-
- const monitorId = selectedMonitor?.monitor?.id;
-
const syntheticExploratoryViewLink = createExploratoryViewUrl(
{
- reportType: 'kpi-over-time',
- allSeries: [
- {
- dataType: 'synthetics',
- seriesType: 'area_stacked',
- selectedMetricField: 'monitor.duration.us',
- time: { from: dateRangeStart, to: dateRangeEnd },
- breakdown: monitorId ? 'observer.geo.name' : 'monitor.type',
- reportDefinitions: {
- 'monitor.name': selectedMonitor?.monitor?.name
- ? [selectedMonitor?.monitor?.name]
- : ['ALL_VALUES'],
- },
- name: monitorId ? `${monitorId}-response-duration` : 'All monitors response duration',
- },
- ],
+ 'synthetics-series': ({
+ dataType: 'synthetics',
+ isNew: true,
+ time: { from: dateRangeStart, to: dateRangeEnd },
+ } as unknown) as SeriesUrl,
},
basePath
);
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
index 18aba948eaa37..1590e225f9ca8 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
@@ -55,19 +55,16 @@ export const MonitorDuration: React.FC = ({ monitorId }) => {
const exploratoryViewLink = createExploratoryViewUrl(
{
- reportType: 'kpi-over-time',
- allSeries: [
- {
- name: `${monitorId}-response-duration`,
- time: { from: dateRangeStart, to: dateRangeEnd },
- reportDefinitions: {
- 'monitor.id': [monitorId] as string[],
- },
- breakdown: 'observer.geo.name',
- operationType: 'average',
- dataType: 'synthetics',
+ [`monitor-duration`]: {
+ reportType: 'kpi-over-time',
+ time: { from: dateRangeStart, to: dateRangeEnd },
+ reportDefinitions: {
+ 'monitor.id': [monitorId] as string[],
},
- ],
+ breakdown: 'observer.geo.name',
+ operationType: 'average',
+ dataType: 'synthetics',
+ },
},
basePath
);
diff --git a/x-pack/test/functional/apps/observability/exploratory_view.ts b/x-pack/test/functional/apps/observability/exploratory_view.ts
deleted file mode 100644
index 8f27f20ce30e6..0000000000000
--- a/x-pack/test/functional/apps/observability/exploratory_view.ts
+++ /dev/null
@@ -1,82 +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 Path from 'path';
-import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const PageObjects = getPageObjects(['observability', 'common', 'header']);
- const esArchiver = getService('esArchiver');
- const find = getService('find');
-
- const testSubjects = getService('testSubjects');
-
- const rangeFrom = '2021-01-17T16%3A46%3A15.338Z';
- const rangeTo = '2021-01-19T17%3A01%3A32.309Z';
-
- // Failing: See https://github.com/elastic/kibana/issues/106934
- describe.skip('ExploratoryView', () => {
- before(async () => {
- await esArchiver.loadIfNeeded(
- Path.join('x-pack/test/apm_api_integration/common/fixtures/es_archiver', '8.0.0')
- );
-
- await esArchiver.loadIfNeeded(
- Path.join('x-pack/test/apm_api_integration/common/fixtures/es_archiver', 'rum_8.0.0')
- );
-
- await esArchiver.loadIfNeeded(
- Path.join('x-pack/test/apm_api_integration/common/fixtures/es_archiver', 'rum_test_data')
- );
-
- await PageObjects.common.navigateToApp('ux', {
- search: `?rangeFrom=${rangeFrom}&rangeTo=${rangeTo}`,
- });
- await PageObjects.header.waitUntilLoadingHasFinished();
- });
-
- after(async () => {
- await esArchiver.unload(
- Path.join('x-pack/test/apm_api_integration/common/fixtures/es_archiver', '8.0.0')
- );
-
- await esArchiver.unload(
- Path.join('x-pack/test/apm_api_integration/common/fixtures/es_archiver', 'rum_8.0.0')
- );
- });
-
- it('should able to open exploratory view from ux app', async () => {
- await testSubjects.exists('uxAnalyzeBtn');
- await testSubjects.click('uxAnalyzeBtn');
- expect(await find.existsByCssSelector('.euiBasicTable')).to.eql(true);
- });
-
- it('renders lens visualization', async () => {
- expect(await testSubjects.exists('lnsVisualizationContainer')).to.eql(true);
-
- expect(
- await find.existsByCssSelector('div[data-title="Prefilled from exploratory view app"]')
- ).to.eql(true);
-
- expect((await find.byCssSelector('dd')).getVisibleText()).to.eql(true);
- });
-
- it('can do a breakdown per series', async () => {
- await testSubjects.click('seriesBreakdown');
-
- expect(await find.existsByCssSelector('[id="user_agent.name"]')).to.eql(true);
-
- await find.clickByCssSelector('[id="user_agent.name"]');
-
- await PageObjects.header.waitUntilLoadingHasFinished();
-
- expect(await find.existsByCssSelector('[title="Chrome Mobile iOS"]')).to.eql(true);
- expect(await find.existsByCssSelector('[title="Mobile Safari"]')).to.eql(true);
- });
- });
-}
diff --git a/x-pack/test/functional/apps/observability/index.ts b/x-pack/test/functional/apps/observability/index.ts
index cce07b9ff7d86..b7f03b5f27bae 100644
--- a/x-pack/test/functional/apps/observability/index.ts
+++ b/x-pack/test/functional/apps/observability/index.ts
@@ -8,9 +8,8 @@
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
- describe('ObservabilityApp', function () {
+ describe('Observability specs', function () {
this.tags('ciGroup6');
loadTestFile(require.resolve('./feature_controls'));
- loadTestFile(require.resolve('./exploratory_view'));
});
}