[Exploratory view] Improve/Polish components usage (#96782)
This commit is contained in:
parent
2fb8b92ec9
commit
c68eab6298
|
@ -38,18 +38,20 @@ export const fetchUxOverviewDate = async ({
|
|||
};
|
||||
};
|
||||
|
||||
export async function hasRumData({
|
||||
absoluteTime,
|
||||
}: HasDataParams): Promise<UXHasDataResponse> {
|
||||
export async function hasRumData(
|
||||
params: HasDataParams
|
||||
): Promise<UXHasDataResponse> {
|
||||
return await callApmApi({
|
||||
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
|
||||
signal: null,
|
||||
params: {
|
||||
query: {
|
||||
start: new Date(absoluteTime.start).toISOString(),
|
||||
end: new Date(absoluteTime.end).toISOString(),
|
||||
uiFilters: '',
|
||||
},
|
||||
query: params?.absoluteTime
|
||||
? {
|
||||
start: new Date(params.absoluteTime.start).toISOString(),
|
||||
end: new Date(params.absoluteTime.end).toISOString(),
|
||||
uiFilters: '',
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -263,7 +263,7 @@ const rumJSErrors = createApmServerRoute({
|
|||
|
||||
const rumHasDataRoute = createApmServerRoute({
|
||||
endpoint: 'GET /api/apm/observability_overview/has_rum_data',
|
||||
params: t.type({
|
||||
params: t.partial({
|
||||
query: t.intersection([uiFiltersRt, rangeRt]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { mockIndexPattern, render } from '../rtl_helpers';
|
||||
import { mockAppIndexPattern, mockIndexPattern, render } from '../rtl_helpers';
|
||||
import { buildFilterLabel, FilterLabel } from './filter_label';
|
||||
import * as useSeriesHook from '../hooks/use_series_filters';
|
||||
|
||||
describe('FilterLabel', function () {
|
||||
mockAppIndexPattern();
|
||||
|
||||
const invertFilter = jest.fn();
|
||||
jest.spyOn(useSeriesHook, 'useSeriesFilters').mockReturnValue({
|
||||
invertFilter,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import { esFilters, Filter, IndexPattern } from '../../../../../../../../src/plugins/data/public';
|
||||
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useSeriesFilters } from '../hooks/use_series_filters';
|
||||
|
||||
|
@ -58,7 +58,7 @@ export function FilterLabel({
|
|||
}: Props) {
|
||||
const FilterItem = injectI18n(esFilters.FilterItem);
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
const filter = buildFilterLabel({ field, value, label, indexPattern, negate });
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { FieldFormat } from '../../types';
|
||||
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
|
||||
|
||||
export const apmFieldFormats: FieldFormat[] = [
|
||||
{
|
||||
field: TRANSACTION_DURATION,
|
||||
format: {
|
||||
id: 'duration',
|
||||
params: {
|
||||
inputFormat: 'microseconds',
|
||||
outputFormat: 'asMilliseconds',
|
||||
outputPrecision: 0,
|
||||
showSuffix: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
|
@ -8,6 +8,7 @@
|
|||
import { ConfigProps, DataSeries } from '../../types';
|
||||
import { FieldLabels } from '../constants';
|
||||
import { buildPhraseFilter } from '../utils';
|
||||
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
|
||||
|
||||
export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
|
||||
return {
|
||||
|
@ -37,7 +38,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr
|
|||
'user_agent.device.name',
|
||||
],
|
||||
filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
|
||||
labels: { ...FieldLabels },
|
||||
labels: { ...FieldLabels, [TRANSACTION_DURATION]: 'Latency' },
|
||||
reportDefinitions: [
|
||||
{
|
||||
field: 'service.name',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { ConfigProps, DataSeries } from '../../types';
|
||||
import { FieldLabels } from '../constants/constants';
|
||||
import { buildPhraseFilter } from '../utils';
|
||||
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
|
||||
|
||||
export function getServiceThroughputLensConfig({
|
||||
seriesId,
|
||||
|
@ -40,7 +41,7 @@ export function getServiceThroughputLensConfig({
|
|||
'user_agent.device.name',
|
||||
],
|
||||
filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
|
||||
labels: { ...FieldLabels },
|
||||
labels: { ...FieldLabels, [TRANSACTION_DURATION]: 'Throughput' },
|
||||
reportDefinitions: [
|
||||
{
|
||||
field: 'service.name',
|
||||
|
|
|
@ -61,10 +61,10 @@ export const ReportToDataTypeMap: Record<ReportViewTypeId, AppDataType> = {
|
|||
upp: 'synthetics',
|
||||
tpt: 'apm',
|
||||
svl: 'apm',
|
||||
kpi: 'rum',
|
||||
pld: 'rum',
|
||||
nwk: 'metrics',
|
||||
mem: 'metrics',
|
||||
logs: 'logs',
|
||||
cpu: 'metrics',
|
||||
kpi: 'ux',
|
||||
pld: 'ux',
|
||||
nwk: 'infra_metrics',
|
||||
mem: 'infra_metrics',
|
||||
logs: 'infra_logs',
|
||||
cpu: 'infra_metrics',
|
||||
};
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
*/
|
||||
|
||||
import { LensAttributes } from './lens_attributes';
|
||||
import { mockIndexPattern } from '../rtl_helpers';
|
||||
import { mockAppIndexPattern, mockIndexPattern } from '../rtl_helpers';
|
||||
import { getDefaultConfigs } from './default_configs';
|
||||
import { sampleAttribute } from './test_data/sample_attribute';
|
||||
import { LCP_FIELD, SERVICE_NAME, USER_AGENT_NAME } from './constants/elasticsearch_fieldnames';
|
||||
|
||||
describe('Lens Attribute', () => {
|
||||
mockAppIndexPattern();
|
||||
|
||||
const reportViewConfig = getDefaultConfigs({
|
||||
reportType: 'pld',
|
||||
indexPattern: mockIndexPattern,
|
||||
|
@ -53,7 +55,6 @@ describe('Lens Attribute', () => {
|
|||
readFromDocValues: true,
|
||||
},
|
||||
fieldName: 'transaction.type',
|
||||
columnType: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -72,7 +73,6 @@ describe('Lens Attribute', () => {
|
|||
readFromDocValues: true,
|
||||
},
|
||||
fieldName: 'transaction.duration.us',
|
||||
columnType: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -45,6 +45,34 @@ function buildNumberColumn(sourceField: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export const parseCustomFieldName = (
|
||||
sourceField: string,
|
||||
reportViewConfig: DataSeries,
|
||||
selectedDefinitions: Record<string, string>
|
||||
) => {
|
||||
let fieldName = sourceField;
|
||||
let columnType;
|
||||
|
||||
const rdf = reportViewConfig.reportDefinitions ?? [];
|
||||
|
||||
const customField = rdf.find(({ field }) => field === fieldName);
|
||||
|
||||
if (customField) {
|
||||
if (selectedDefinitions[fieldName]) {
|
||||
fieldName = selectedDefinitions[fieldName];
|
||||
if (customField?.options)
|
||||
columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
|
||||
} else if (customField.defaultValue) {
|
||||
fieldName = customField.defaultValue;
|
||||
} else if (customField.options?.[0].field) {
|
||||
fieldName = customField.options?.[0].field;
|
||||
columnType = customField.options?.[0].columnType;
|
||||
}
|
||||
}
|
||||
|
||||
return { fieldName, columnType };
|
||||
};
|
||||
|
||||
export class LensAttributes {
|
||||
indexPattern: IndexPattern;
|
||||
layers: Record<string, PersistedIndexPatternLayer>;
|
||||
|
@ -124,6 +152,18 @@ export class LensAttributes {
|
|||
};
|
||||
}
|
||||
|
||||
getNumberColumn(sourceField: string, columnType?: string, operationType?: string) {
|
||||
if (columnType === 'operation' || operationType) {
|
||||
if (operationType === 'median' || operationType === 'average') {
|
||||
return this.getNumberOperationColumn(sourceField, operationType);
|
||||
}
|
||||
if (operationType?.includes('th')) {
|
||||
return this.getPercentileNumberColumn(sourceField, operationType);
|
||||
}
|
||||
}
|
||||
return this.getNumberRangeColumn(sourceField);
|
||||
}
|
||||
|
||||
getNumberOperationColumn(
|
||||
sourceField: string,
|
||||
operationType: 'average' | 'median'
|
||||
|
@ -149,7 +189,7 @@ export class LensAttributes {
|
|||
...buildNumberColumn(sourceField),
|
||||
label: i18n.translate('xpack.observability.expView.columns.label', {
|
||||
defaultMessage: '{percentileValue} percentile of {sourceField}',
|
||||
values: { sourceField, percentileValue },
|
||||
values: { sourceField: this.reportViewConfig.labels[sourceField], percentileValue },
|
||||
}),
|
||||
operationType: 'percentile',
|
||||
params: { percentile: Number(percentileValue.split('th')[0]) },
|
||||
|
@ -186,15 +226,7 @@ export class LensAttributes {
|
|||
return this.getDateHistogramColumn(fieldName);
|
||||
}
|
||||
if (fieldType === 'number') {
|
||||
if (columnType === 'operation' || operationType) {
|
||||
if (operationType === 'median' || operationType === 'average') {
|
||||
return this.getNumberOperationColumn(fieldName, operationType);
|
||||
}
|
||||
if (operationType?.includes('th')) {
|
||||
return this.getPercentileNumberColumn(sourceField, operationType);
|
||||
}
|
||||
}
|
||||
return this.getNumberRangeColumn(fieldName);
|
||||
return this.getNumberColumn(fieldName, columnType, operationType);
|
||||
}
|
||||
|
||||
// FIXME review my approach again
|
||||
|
@ -202,27 +234,7 @@ export class LensAttributes {
|
|||
}
|
||||
|
||||
getCustomFieldName(sourceField: string) {
|
||||
let fieldName = sourceField;
|
||||
let columnType = null;
|
||||
|
||||
const rdf = this.reportViewConfig.reportDefinitions ?? [];
|
||||
|
||||
const customField = rdf.find(({ field }) => field === fieldName);
|
||||
|
||||
if (customField) {
|
||||
if (this.reportDefinitions[fieldName]) {
|
||||
fieldName = this.reportDefinitions[fieldName];
|
||||
if (customField?.options)
|
||||
columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
|
||||
} else if (customField.defaultValue) {
|
||||
fieldName = customField.defaultValue;
|
||||
} else if (customField.options?.[0].field) {
|
||||
fieldName = customField.options?.[0].field;
|
||||
columnType = customField.options?.[0].columnType;
|
||||
}
|
||||
}
|
||||
|
||||
return { fieldName, columnType };
|
||||
return parseCustomFieldName(sourceField, this.reportViewConfig, this.reportDefinitions);
|
||||
}
|
||||
|
||||
getFieldMeta(sourceField: string) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
USER_AGENT_OS,
|
||||
USER_AGENT_VERSION,
|
||||
TRANSACTION_TIME_TO_FIRST_BYTE,
|
||||
TRANSACTION_URL,
|
||||
} from '../constants/elasticsearch_fieldnames';
|
||||
|
||||
export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
|
||||
|
@ -42,6 +43,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
|
|||
},
|
||||
hasOperationType: false,
|
||||
defaultFilters: [
|
||||
{
|
||||
field: TRANSACTION_URL,
|
||||
isNegated: false,
|
||||
},
|
||||
USER_AGENT_OS,
|
||||
CLIENT_GEO_COUNTRY_NAME,
|
||||
USER_AGENT_DEVICE,
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
TRANSACTION_DURATION,
|
||||
TRANSACTION_TIME_TO_FIRST_BYTE,
|
||||
TRANSACTION_TYPE,
|
||||
TRANSACTION_URL,
|
||||
USER_AGENT_DEVICE,
|
||||
USER_AGENT_NAME,
|
||||
USER_AGENT_OS,
|
||||
|
@ -42,6 +43,10 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
|
|||
},
|
||||
hasOperationType: false,
|
||||
defaultFilters: [
|
||||
{
|
||||
field: TRANSACTION_URL,
|
||||
isNegated: false,
|
||||
},
|
||||
USER_AGENT_OS,
|
||||
CLIENT_GEO_COUNTRY_NAME,
|
||||
USER_AGENT_DEVICE,
|
||||
|
|
|
@ -42,6 +42,6 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries {
|
|||
field: 'monitor.id',
|
||||
},
|
||||
],
|
||||
labels: { ...FieldLabels },
|
||||
labels: { ...FieldLabels, 'monitor.duration.us': 'Monitor duration' },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/dom';
|
||||
import { render, mockUrlStorage, mockCore } from './rtl_helpers';
|
||||
import { render, mockUrlStorage, 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';
|
||||
|
||||
describe('ExploratoryView', () => {
|
||||
mockAppIndexPattern();
|
||||
|
||||
beforeEach(() => {
|
||||
const indexPattern = getStubIndexPattern(
|
||||
'apm-*',
|
||||
|
|
|
@ -15,6 +15,8 @@ import { useUrlStorage } from './hooks/use_url_storage';
|
|||
import { useLensAttributes } from './hooks/use_lens_attributes';
|
||||
import { EmptyView } from './components/empty_view';
|
||||
import { TypedLensByValueInput } from '../../../../../lens/public';
|
||||
import { useAppIndexPatternContext } from './hooks/use_app_index_pattern';
|
||||
import { ReportToDataTypeMap } from './configurations/constants';
|
||||
|
||||
export function ExploratoryView() {
|
||||
const {
|
||||
|
@ -25,6 +27,8 @@ export function ExploratoryView() {
|
|||
null
|
||||
);
|
||||
|
||||
const { loadIndexPattern } = useAppIndexPatternContext();
|
||||
|
||||
const LensComponent = lens?.EmbeddableComponent;
|
||||
|
||||
const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage();
|
||||
|
@ -33,13 +37,19 @@ export function ExploratoryView() {
|
|||
seriesId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (series?.reportType || series?.dataType) {
|
||||
loadIndexPattern({ dataType: series?.dataType ?? ReportToDataTypeMap[series?.reportType] });
|
||||
}
|
||||
}, [series?.reportType, series?.dataType, loadIndexPattern]);
|
||||
|
||||
useEffect(() => {
|
||||
setLensAttributes(lensAttributesT);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(lensAttributesT ?? {}), series?.reportType, series?.time?.from]);
|
||||
|
||||
return (
|
||||
<EuiPanel style={{ maxWidth: 1800, minWidth: 1200, margin: '0 auto' }}>
|
||||
<EuiPanel style={{ maxWidth: 1800, minWidth: 800, margin: '0 auto' }}>
|
||||
{lens ? (
|
||||
<>
|
||||
<ExploratoryViewHeader lensAttributes={lensAttributes} seriesId={seriesId} />
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext, Context, useState, useCallback, useMemo } from 'react';
|
||||
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
import { AppDataType } from '../types';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
|
||||
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
|
||||
import { getDataHandler } from '../../../../data_handler';
|
||||
import { UXHasDataResponse } from '../../../../typings/fetch_overview_data';
|
||||
|
||||
export interface IIndexPatternContext {
|
||||
loading: boolean;
|
||||
selectedApp: AppDataType;
|
||||
indexPatterns: IndexPatternState;
|
||||
hasAppData: HasAppDataState;
|
||||
loadIndexPattern: (params: { dataType: AppDataType }) => void;
|
||||
}
|
||||
|
||||
export const IndexPatternContext = createContext<Partial<IIndexPatternContext>>({});
|
||||
|
||||
interface ProviderProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
type HasAppDataState = Record<AppDataType, boolean | null>;
|
||||
type IndexPatternState = Record<AppDataType, IndexPattern>;
|
||||
|
||||
export function IndexPatternContextProvider({ children }: ProviderProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedApp, setSelectedApp] = useState<AppDataType>();
|
||||
const [indexPatterns, setIndexPatterns] = useState<IndexPatternState>({} as IndexPatternState);
|
||||
const [hasAppData, setHasAppData] = useState<HasAppDataState>({
|
||||
infra_metrics: null,
|
||||
infra_logs: null,
|
||||
synthetics: null,
|
||||
ux: null,
|
||||
apm: null,
|
||||
} as HasAppDataState);
|
||||
|
||||
const {
|
||||
services: { data },
|
||||
} = useKibana<ObservabilityPublicPluginsStart>();
|
||||
|
||||
const checkIfAppHasData = async (dataType: AppDataType) => {
|
||||
const handler = getDataHandler(dataType === 'synthetics' ? 'uptime' : dataType);
|
||||
return handler?.hasData();
|
||||
};
|
||||
|
||||
const loadIndexPattern: IIndexPatternContext['loadIndexPattern'] = useCallback(
|
||||
async ({ dataType }) => {
|
||||
setSelectedApp(dataType);
|
||||
|
||||
if (hasAppData[dataType] === null) {
|
||||
setLoading(true);
|
||||
try {
|
||||
let hasDataT = await checkIfAppHasData(dataType);
|
||||
|
||||
if (dataType === 'ux') {
|
||||
hasDataT = (hasDataT as UXHasDataResponse).hasData as boolean;
|
||||
}
|
||||
|
||||
setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT }));
|
||||
|
||||
if (hasDataT || hasAppData?.[dataType]) {
|
||||
const obsvIndexP = new ObservabilityIndexPatterns(data);
|
||||
const indPattern = await obsvIndexP.getIndexPattern(dataType);
|
||||
|
||||
setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern }));
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[data, hasAppData]
|
||||
);
|
||||
|
||||
return (
|
||||
<IndexPatternContext.Provider
|
||||
value={{
|
||||
loading,
|
||||
hasAppData,
|
||||
selectedApp,
|
||||
indexPatterns,
|
||||
loadIndexPattern,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</IndexPatternContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useAppIndexPatternContext = () => {
|
||||
const { selectedApp, loading, hasAppData, loadIndexPattern, indexPatterns } = useContext(
|
||||
(IndexPatternContext as unknown) as Context<IIndexPatternContext>
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
hasAppData,
|
||||
selectedApp,
|
||||
loading,
|
||||
indexPattern: indexPatterns?.[selectedApp],
|
||||
hasData: hasAppData?.[selectedApp],
|
||||
loadIndexPattern,
|
||||
};
|
||||
}, [hasAppData, indexPatterns, loadIndexPattern, loading, selectedApp]);
|
||||
};
|
|
@ -1,62 +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, { createContext, useContext, Context, useState, useEffect } from 'react';
|
||||
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
import { AppDataType } from '../types';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
|
||||
import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
|
||||
|
||||
export interface IIndexPatternContext {
|
||||
indexPattern: IndexPattern;
|
||||
loadIndexPattern: (dataType: AppDataType) => void;
|
||||
}
|
||||
|
||||
export const IndexPatternContext = createContext<Partial<IIndexPatternContext>>({});
|
||||
|
||||
interface ProviderProps {
|
||||
indexPattern?: IndexPattern;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export function IndexPatternContextProvider({
|
||||
children,
|
||||
indexPattern: initialIndexPattern,
|
||||
}: ProviderProps) {
|
||||
const [indexPattern, setIndexPattern] = useState(initialIndexPattern);
|
||||
|
||||
useEffect(() => {
|
||||
setIndexPattern(initialIndexPattern);
|
||||
}, [initialIndexPattern]);
|
||||
|
||||
const {
|
||||
services: { data },
|
||||
} = useKibana<ObservabilityPublicPluginsStart>();
|
||||
|
||||
const loadIndexPattern = async (dataType: AppDataType) => {
|
||||
setIndexPattern(undefined);
|
||||
const obsvIndexP = new ObservabilityIndexPatterns(data);
|
||||
const indPattern = await obsvIndexP.getIndexPattern(dataType);
|
||||
setIndexPattern(indPattern!);
|
||||
};
|
||||
|
||||
return (
|
||||
<IndexPatternContext.Provider
|
||||
value={{
|
||||
indexPattern,
|
||||
loadIndexPattern,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</IndexPatternContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useIndexPatternContext = () => {
|
||||
return useContext((IndexPatternContext as unknown) as Context<IIndexPatternContext>);
|
||||
};
|
|
@ -1,47 +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 { useFetcher } from '../../../..';
|
||||
import { IKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../../plugin';
|
||||
import { AllShortSeries } from './use_url_storage';
|
||||
import { ReportToDataTypeMap } from '../configurations/constants';
|
||||
import { DataType, ObservabilityIndexPatterns } from '../utils/observability_index_patterns';
|
||||
|
||||
export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => {
|
||||
const {
|
||||
services: { data },
|
||||
} = useKibana<ObservabilityPublicPluginsStart>();
|
||||
|
||||
const allSeriesKey = 'sr';
|
||||
|
||||
const allSeries = storage.get<AllShortSeries>(allSeriesKey) ?? {};
|
||||
|
||||
const allSeriesIds = Object.keys(allSeries);
|
||||
|
||||
const firstSeriesId = allSeriesIds?.[0];
|
||||
|
||||
const firstSeries = allSeries[firstSeriesId];
|
||||
|
||||
let dataType: DataType = firstSeries?.dataType ?? 'rum';
|
||||
|
||||
if (firstSeries?.rt) {
|
||||
dataType = ReportToDataTypeMap[firstSeries?.rt];
|
||||
}
|
||||
|
||||
const { data: indexPattern, error } = useFetcher(() => {
|
||||
const obsvIndexP = new ObservabilityIndexPatterns(data);
|
||||
|
||||
return obsvIndexP.getIndexPattern(dataType);
|
||||
}, [dataType, data]);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return indexPattern;
|
||||
};
|
|
@ -12,7 +12,7 @@ import { useUrlStorage } from './use_url_storage';
|
|||
import { getDefaultConfigs } from '../configurations/default_configs';
|
||||
|
||||
import { DataSeries, SeriesUrl, UrlFilter } from '../types';
|
||||
import { useIndexPatternContext } from './use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from './use_app_index_pattern';
|
||||
|
||||
interface Props {
|
||||
seriesId: string;
|
||||
|
@ -43,7 +43,7 @@ export const useLensAttributes = ({
|
|||
|
||||
const { breakdown, seriesType, operationType, reportType, reportDefinitions = {} } = series ?? {};
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
return useMemo(() => {
|
||||
if (!indexPattern || !reportType) {
|
||||
|
|
|
@ -56,7 +56,7 @@ interface ShortUrlSeries {
|
|||
export type AllShortSeries = Record<string, ShortUrlSeries>;
|
||||
export type AllSeries = Record<string, SeriesUrl>;
|
||||
|
||||
export const NEW_SERIES_KEY = 'newSeriesKey';
|
||||
export const NEW_SERIES_KEY = 'new-series-key';
|
||||
|
||||
export function useUrlStorage(seriesId?: string) {
|
||||
const allSeriesKey = 'sr';
|
||||
|
|
|
@ -13,13 +13,12 @@ import { ExploratoryView } from './exploratory_view';
|
|||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
|
||||
import { IndexPatternContextProvider } from './hooks/use_default_index_pattern';
|
||||
import { IndexPatternContextProvider } from './hooks/use_app_index_pattern';
|
||||
import {
|
||||
createKbnUrlStateStorage,
|
||||
withNotifyOnErrors,
|
||||
} from '../../../../../../../src/plugins/kibana_utils/public/';
|
||||
import { UrlStorageContextProvider } from './hooks/use_url_storage';
|
||||
import { useInitExploratoryView } from './hooks/use_init_exploratory_view';
|
||||
import { WithHeaderLayout } from '../../app/layout/with_header';
|
||||
|
||||
export function ExploratoryViewPage() {
|
||||
|
@ -45,20 +44,16 @@ export function ExploratoryViewPage() {
|
|||
...withNotifyOnErrors(notifications!.toasts),
|
||||
});
|
||||
|
||||
const indexPattern = useInitExploratoryView(kbnUrlStateStorage);
|
||||
|
||||
return (
|
||||
<WithHeaderLayout
|
||||
headerColor={theme.eui.euiColorEmptyShade}
|
||||
bodyColor={theme.eui.euiPageBackgroundColor}
|
||||
>
|
||||
{indexPattern ? (
|
||||
<IndexPatternContextProvider indexPattern={indexPattern!}>
|
||||
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
|
||||
<ExploratoryView />
|
||||
</UrlStorageContextProvider>
|
||||
</IndexPatternContextProvider>
|
||||
) : null}
|
||||
<IndexPatternContextProvider>
|
||||
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
|
||||
<ExploratoryView />
|
||||
</UrlStorageContextProvider>
|
||||
</IndexPatternContextProvider>
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { lensPluginMock } from '../../../../../lens/public/mocks';
|
||||
import { IndexPatternContextProvider } from './hooks/use_default_index_pattern';
|
||||
import { IndexPatternContextProvider } from './hooks/use_app_index_pattern';
|
||||
import { AllSeries, UrlStorageContextProvider } from './hooks/use_url_storage';
|
||||
import {
|
||||
withNotifyOnErrors,
|
||||
|
@ -33,6 +33,7 @@ import * as useUrlHook from './hooks/use_url_storage';
|
|||
import * as useSeriesFilterHook from './hooks/use_series_filters';
|
||||
import * as useHasDataHook from '../../../hooks/use_has_data';
|
||||
import * as useValuesListHook from '../../../hooks/use_values_list';
|
||||
import * as useAppIndexPatternHook from './hooks/use_app_index_pattern';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getStubIndexPattern } from '../../../../../../../src/plugins/data/public/index_patterns/index_pattern.stub';
|
||||
|
@ -148,7 +149,7 @@ export function MockKibanaProvider<ExtraCore extends Partial<CoreStart>>({
|
|||
<KibanaContextProvider services={{ ...core }} {...kibanaProps}>
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<I18nProvider>
|
||||
<IndexPatternContextProvider indexPattern={indexPattern}>
|
||||
<IndexPatternContextProvider>
|
||||
<UrlStorageContextProvider storage={kbnUrlStateStorage}>
|
||||
{children}
|
||||
</UrlStorageContextProvider>
|
||||
|
@ -234,6 +235,19 @@ export const mockUseHasData = () => {
|
|||
return { spy, onRefreshTimeRange };
|
||||
};
|
||||
|
||||
export const mockAppIndexPattern = () => {
|
||||
const loadIndexPattern = jest.fn();
|
||||
const spy = jest.spyOn(useAppIndexPatternHook, 'useAppIndexPatternContext').mockReturnValue({
|
||||
indexPattern: mockIndexPattern,
|
||||
selectedApp: 'ux',
|
||||
hasData: true,
|
||||
loading: false,
|
||||
hasAppData: { ux: true } as any,
|
||||
loadIndexPattern,
|
||||
});
|
||||
return { spy, loadIndexPattern };
|
||||
};
|
||||
|
||||
export const mockUseValuesList = (values?: string[]) => {
|
||||
const onRefreshTimeRange = jest.fn();
|
||||
const spy = jest.spyOn(useValuesListHook, 'useValuesList').mockReturnValue({
|
||||
|
|
|
@ -66,7 +66,7 @@ export function XYChartTypesSelect({
|
|||
|
||||
const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]);
|
||||
|
||||
let vizTypes = data ?? [];
|
||||
let vizTypes = data;
|
||||
|
||||
if ((excludeChartTypes ?? []).length > 0) {
|
||||
vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id as SeriesType));
|
||||
|
@ -95,6 +95,7 @@ export function XYChartTypesSelect({
|
|||
return (
|
||||
<EuiSuperSelect
|
||||
compressed
|
||||
prepend="Chart type"
|
||||
valueOfSelected={value}
|
||||
isLoading={loading}
|
||||
options={options}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { mockUrlStorage, render } from '../../rtl_helpers';
|
||||
import { mockAppIndexPattern, mockUrlStorage, render } from '../../rtl_helpers';
|
||||
import { dataTypes, DataTypesCol } from './data_types_col';
|
||||
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
|
||||
|
||||
describe('DataTypesCol', function () {
|
||||
mockAppIndexPattern();
|
||||
|
||||
it('should render properly', function () {
|
||||
const { getByText } = render(<DataTypesCol />);
|
||||
|
||||
|
@ -28,11 +30,11 @@ describe('DataTypesCol', function () {
|
|||
fireEvent.click(screen.getByText(/user experience\(rum\)/i));
|
||||
|
||||
expect(setSeries).toHaveBeenCalledTimes(1);
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'rum' });
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, { dataType: 'ux' });
|
||||
});
|
||||
|
||||
it('should set series on change on already selected', function () {
|
||||
const { removeSeries } = mockUrlStorage({
|
||||
mockUrlStorage({
|
||||
data: {
|
||||
[NEW_SERIES_KEY]: {
|
||||
dataType: 'synthetics',
|
||||
|
@ -50,10 +52,5 @@ describe('DataTypesCol', function () {
|
|||
});
|
||||
|
||||
expect(button.classList).toContain('euiButton--fill');
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
// undefined on click selected
|
||||
expect(removeSeries).toHaveBeenCalledWith('newSeriesKey');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,27 +7,25 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { AppDataType } from '../../types';
|
||||
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
|
||||
|
||||
export const dataTypes: Array<{ id: AppDataType; label: string }> = [
|
||||
{ id: 'synthetics', label: 'Synthetic Monitoring' },
|
||||
{ id: 'rum', label: 'User Experience(RUM)' },
|
||||
{ id: 'logs', label: 'Logs' },
|
||||
{ id: 'metrics', label: 'Metrics' },
|
||||
{ id: 'apm', label: 'APM' },
|
||||
{ id: 'ux', label: 'User Experience(RUM)' },
|
||||
// { id: 'infra_logs', label: 'Logs' },
|
||||
// { id: 'infra_metrics', label: 'Metrics' },
|
||||
// { id: 'apm', label: 'APM' },
|
||||
];
|
||||
|
||||
export function DataTypesCol() {
|
||||
const { series, setSeries, removeSeries } = useUrlStorage(NEW_SERIES_KEY);
|
||||
|
||||
const { loadIndexPattern, indexPattern } = useIndexPatternContext();
|
||||
const { loading } = useAppIndexPatternContext();
|
||||
|
||||
const onDataTypeChange = (dataType?: AppDataType) => {
|
||||
if (dataType) {
|
||||
loadIndexPattern(dataType);
|
||||
}
|
||||
if (!dataType) {
|
||||
removeSeries(NEW_SERIES_KEY);
|
||||
} else {
|
||||
|
@ -38,7 +36,7 @@ export function DataTypesCol() {
|
|||
const selectedDataType = series.dataType;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<FlexGroup direction="column" gutterSize="xs">
|
||||
{dataTypes.map(({ id: dataTypeId, label }) => (
|
||||
<EuiFlexItem key={dataTypeId}>
|
||||
<EuiButton
|
||||
|
@ -47,16 +45,20 @@ export function DataTypesCol() {
|
|||
iconType="arrowRight"
|
||||
color={selectedDataType === dataTypeId ? 'primary' : 'text'}
|
||||
fill={selectedDataType === dataTypeId}
|
||||
isDisabled={!indexPattern}
|
||||
isLoading={!indexPattern && selectedDataType === dataTypeId}
|
||||
isDisabled={loading}
|
||||
isLoading={loading && selectedDataType === dataTypeId}
|
||||
onClick={() => {
|
||||
onDataTypeChange(dataTypeId === selectedDataType ? undefined : dataTypeId);
|
||||
onDataTypeChange(dataTypeId);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</FlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { SeriesDatePicker } from '../../series_date_picker';
|
||||
|
||||
interface Props {
|
||||
seriesId: string;
|
||||
}
|
||||
export function DatePickerCol({ seriesId }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<SeriesDatePicker seriesId={seriesId} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -29,7 +29,9 @@ export function OperationTypeSelect({
|
|||
|
||||
useEffect(() => {
|
||||
setSeries(seriesId, { ...series, operationType: operationType || defaultOperationType });
|
||||
}, [defaultOperationType, seriesId, operationType, setSeries, series]);
|
||||
// We only want to call this when defaultOperationType changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultOperationType]);
|
||||
|
||||
const options = [
|
||||
{
|
||||
|
@ -72,6 +74,7 @@ export function OperationTypeSelect({
|
|||
|
||||
return (
|
||||
<EuiSuperSelect
|
||||
prepend="Calculation"
|
||||
data-test-subj="operationTypeSelect"
|
||||
compressed
|
||||
valueOfSelected={operationType || defaultOperationType}
|
||||
|
|
|
@ -45,7 +45,7 @@ describe('Series Builder ReportBreakdowns', function () {
|
|||
fireEvent.click(screen.getByText(/operating system/i));
|
||||
|
||||
expect(setSeries).toHaveBeenCalledTimes(1);
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
|
||||
breakdown: USER_AGENT_OS,
|
||||
reportType: 'pld',
|
||||
time: { from: 'now-15m', to: 'now' },
|
||||
|
@ -66,7 +66,7 @@ describe('Series Builder ReportBreakdowns', function () {
|
|||
fireEvent.click(screen.getByText(/no breakdown/i));
|
||||
|
||||
expect(setSeries).toHaveBeenCalledTimes(1);
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
|
||||
breakdown: undefined,
|
||||
reportType: 'pld',
|
||||
time: { from: 'now-15m', to: 'now' },
|
||||
|
|
|
@ -8,12 +8,20 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { getDefaultConfigs } from '../../configurations/default_configs';
|
||||
import { mockIndexPattern, mockUrlStorage, mockUseValuesList, render } from '../../rtl_helpers';
|
||||
import {
|
||||
mockAppIndexPattern,
|
||||
mockIndexPattern,
|
||||
mockUrlStorage,
|
||||
mockUseValuesList,
|
||||
render,
|
||||
} from '../../rtl_helpers';
|
||||
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
|
||||
import { ReportDefinitionCol } from './report_definition_col';
|
||||
import { SERVICE_NAME } from '../../configurations/constants/elasticsearch_fieldnames';
|
||||
|
||||
describe('Series Builder ReportDefinitionCol', function () {
|
||||
mockAppIndexPattern();
|
||||
|
||||
const dataViewSeries = getDefaultConfigs({
|
||||
reportType: 'pld',
|
||||
indexPattern: mockIndexPattern,
|
||||
|
@ -23,7 +31,7 @@ describe('Series Builder ReportDefinitionCol', function () {
|
|||
const { setSeries } = mockUrlStorage({
|
||||
data: {
|
||||
'performance-dist': {
|
||||
dataType: 'rum',
|
||||
dataType: 'ux',
|
||||
reportType: 'pld',
|
||||
time: { from: 'now-30d', to: 'now' },
|
||||
reportDefinitions: { [SERVICE_NAME]: 'elastic-co' },
|
||||
|
@ -54,8 +62,8 @@ describe('Series Builder ReportDefinitionCol', function () {
|
|||
fireEvent.click(removeBtn);
|
||||
|
||||
expect(setSeries).toHaveBeenCalledTimes(1);
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
|
||||
dataType: 'rum',
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
|
||||
dataType: 'ux',
|
||||
reportDefinitions: {},
|
||||
reportType: 'pld',
|
||||
time: { from: 'now-30d', to: 'now' },
|
||||
|
|
|
@ -7,16 +7,32 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
|
||||
import styled from 'styled-components';
|
||||
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
|
||||
import { CustomReportField } from '../custom_report_field';
|
||||
import FieldValueSuggestions from '../../../field_value_suggestions';
|
||||
import { DataSeries } from '../../types';
|
||||
import { SeriesChartTypesSelect } from './chart_types';
|
||||
import { OperationTypeSelect } from './operation_type_select';
|
||||
import { DatePickerCol } from './date_picker_col';
|
||||
import { parseCustomFieldName } from '../../configurations/lens_attributes';
|
||||
|
||||
function getColumnType(dataView: DataSeries, selectedDefinition: Record<string, string>) {
|
||||
const { reportDefinitions } = dataView;
|
||||
const customColumn = reportDefinitions.find((item) => item.custom);
|
||||
if (customColumn?.field && selectedDefinition[customColumn?.field]) {
|
||||
const { columnType } = parseCustomFieldName(customColumn.field, dataView, selectedDefinition);
|
||||
|
||||
return columnType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const MaxWidthStyle = { maxWidth: 250 };
|
||||
|
||||
export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSeries }) {
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY);
|
||||
|
||||
|
@ -54,14 +70,19 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
|
|||
});
|
||||
};
|
||||
|
||||
const columnType = getColumnType(dataViewSeries, rtd);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<FlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<DatePickerCol seriesId={NEW_SERIES_KEY} />
|
||||
</EuiFlexItem>
|
||||
{indexPattern &&
|
||||
reportDefinitions.map(({ field, custom, options, defaultValue }) => (
|
||||
<EuiFlexItem key={field}>
|
||||
{!custom ? (
|
||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="s" alignItems="center" wrap>
|
||||
<EuiFlexItem grow={false} style={{ flexBasis: 250 }}>
|
||||
<FieldValueSuggestions
|
||||
label={labels[field]}
|
||||
sourceField={field}
|
||||
|
@ -70,7 +91,7 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
|
|||
onChange={(val?: string) => onChange(field, val)}
|
||||
filters={(filters ?? []).map(({ query }) => query)}
|
||||
time={series.time}
|
||||
width={200}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{rtd?.[field] && (
|
||||
|
@ -100,17 +121,21 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
<EuiFlexItem style={{ width: 200 }}>
|
||||
<SeriesChartTypesSelect seriesId={NEW_SERIES_KEY} defaultChartType={defaultSeriesType} />
|
||||
</EuiFlexItem>
|
||||
{hasOperationType && (
|
||||
<EuiFlexItem style={{ width: 200 }}>
|
||||
{(hasOperationType || columnType === 'operation') && (
|
||||
<EuiFlexItem style={MaxWidthStyle}>
|
||||
<OperationTypeSelect
|
||||
seriesId={NEW_SERIES_KEY}
|
||||
defaultOperationType={yAxisColumn.operationType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem style={MaxWidthStyle}>
|
||||
<SeriesChartTypesSelect seriesId={NEW_SERIES_KEY} defaultChartType={defaultSeriesType} />
|
||||
</EuiFlexItem>
|
||||
</FlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { mockUrlStorage, render } from '../../rtl_helpers';
|
||||
import { mockAppIndexPattern, mockUrlStorage, render } from '../../rtl_helpers';
|
||||
import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col';
|
||||
import { ReportTypes } from '../series_builder';
|
||||
import { DEFAULT_TIME } from '../../configurations/constants';
|
||||
import { NEW_SERIES_KEY } from '../../hooks/use_url_storage';
|
||||
|
||||
describe('ReportTypesCol', function () {
|
||||
mockAppIndexPattern();
|
||||
|
||||
it('should render properly', function () {
|
||||
render(<ReportTypesCol reportTypes={ReportTypes.rum} />);
|
||||
render(<ReportTypesCol reportTypes={ReportTypes.ux} />);
|
||||
screen.getByText('Performance distribution');
|
||||
screen.getByText('KPI over time');
|
||||
});
|
||||
|
@ -30,7 +33,7 @@ describe('ReportTypesCol', function () {
|
|||
|
||||
fireEvent.click(screen.getByText(/monitor duration/i));
|
||||
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
|
||||
breakdown: 'user_agent.name',
|
||||
reportDefinitions: {},
|
||||
reportType: 'upd',
|
||||
|
@ -42,7 +45,7 @@ describe('ReportTypesCol', function () {
|
|||
it('should set selected as filled', function () {
|
||||
const { setSeries } = mockUrlStorage({
|
||||
data: {
|
||||
newSeriesKey: {
|
||||
[NEW_SERIES_KEY]: {
|
||||
dataType: 'synthetics',
|
||||
reportType: 'upp',
|
||||
breakdown: 'monitor.status',
|
||||
|
@ -61,7 +64,7 @@ describe('ReportTypesCol', function () {
|
|||
fireEvent.click(button);
|
||||
|
||||
// undefined on click selected
|
||||
expect(setSeries).toHaveBeenCalledWith('newSeriesKey', {
|
||||
expect(setSeries).toHaveBeenCalledWith(NEW_SERIES_KEY, {
|
||||
dataType: 'synthetics',
|
||||
time: DEFAULT_TIME,
|
||||
});
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { ReportViewTypeId, SeriesUrl } from '../../types';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
|
||||
import { DEFAULT_TIME } from '../../configurations/constants';
|
||||
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
|
||||
|
||||
interface Props {
|
||||
reportTypes: Array<{ id: ReportViewTypeId; label: string }>;
|
||||
|
@ -23,19 +25,29 @@ export function ReportTypesCol({ reportTypes }: Props) {
|
|||
setSeries,
|
||||
} = useUrlStorage(NEW_SERIES_KEY);
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { loading, hasData, selectedApp } = useAppIndexPatternContext();
|
||||
|
||||
if (!loading && !hasData && selectedApp) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.observability.reportTypeCol.nodata"
|
||||
defaultMessage="No data available"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return reportTypes?.length > 0 ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<FlexGroup direction="column" gutterSize="xs">
|
||||
{reportTypes.map(({ id: reportType, label }) => (
|
||||
<EuiFlexItem key={reportType}>
|
||||
<EuiButton
|
||||
fullWidth
|
||||
size="s"
|
||||
iconSide="right"
|
||||
iconType="arrowRight"
|
||||
color={selectedReportType === reportType ? 'primary' : 'text'}
|
||||
fill={selectedReportType === reportType}
|
||||
isDisabled={!indexPattern}
|
||||
isDisabled={loading}
|
||||
onClick={() => {
|
||||
if (reportType === selectedReportType) {
|
||||
setSeries(NEW_SERIES_KEY, {
|
||||
|
@ -56,7 +68,7 @@ export function ReportTypesCol({ reportTypes }: Props) {
|
|||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</FlexGroup>
|
||||
) : (
|
||||
<EuiText color="subdued">{SELECTED_DATA_TYPE_FOR_REPORT}</EuiText>
|
||||
);
|
||||
|
@ -66,3 +78,7 @@ export const SELECTED_DATA_TYPE_FOR_REPORT = i18n.translate(
|
|||
'xpack.observability.expView.reportType.noDataType',
|
||||
{ defaultMessage: 'Select a data type to start building a series.' }
|
||||
);
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
|
|
@ -28,18 +28,18 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue
|
|||
|
||||
const { reportDefinitions } = series;
|
||||
|
||||
const NO_SELECT = 'no_select';
|
||||
|
||||
const options = [{ label: 'Select metric', field: NO_SELECT }, ...(opts ?? [])];
|
||||
const options = opts ?? [];
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 200 }}>
|
||||
<div style={{ maxWidth: 250 }}>
|
||||
<EuiSuperSelect
|
||||
compressed
|
||||
prepend={'Metric'}
|
||||
options={options.map(({ label, field: fd, description }) => ({
|
||||
value: fd,
|
||||
inputDisplay: label,
|
||||
}))}
|
||||
valueOfSelected={reportDefinitions?.[field] || defaultValue || NO_SELECT}
|
||||
valueOfSelected={reportDefinitions?.[field] || defaultValue || options?.[0].field}
|
||||
onChange={(value) => onChange(value)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton, EuiBasicTable, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { AppDataType, ReportViewTypeId, ReportViewTypes, SeriesUrl } from '../types';
|
||||
import { DataTypesCol } from './columns/data_types_col';
|
||||
import { ReportTypesCol } from './columns/report_types_col';
|
||||
|
@ -17,7 +16,7 @@ import { ReportDefinitionCol } from './columns/report_definition_col';
|
|||
import { ReportFilters } from './columns/report_filters';
|
||||
import { ReportBreakdowns } from './columns/report_breakdowns';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
|
||||
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
|
||||
import { getDefaultConfigs } from '../configurations/default_configs';
|
||||
|
||||
export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; label: string }>> = {
|
||||
|
@ -25,7 +24,7 @@ export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; labe
|
|||
{ id: 'upd', label: 'Monitor duration' },
|
||||
{ id: 'upp', label: 'Pings histogram' },
|
||||
],
|
||||
rum: [
|
||||
ux: [
|
||||
{ id: 'pld', label: 'Performance distribution' },
|
||||
{ id: 'kpi', label: 'KPI over time' },
|
||||
],
|
||||
|
@ -33,13 +32,13 @@ export const ReportTypes: Record<AppDataType, Array<{ id: ReportViewTypeId; labe
|
|||
{ id: 'svl', label: 'Latency' },
|
||||
{ id: 'tpt', label: 'Throughput' },
|
||||
],
|
||||
logs: [
|
||||
infra_logs: [
|
||||
{
|
||||
id: 'logs',
|
||||
label: 'Logs Frequency',
|
||||
},
|
||||
],
|
||||
metrics: [
|
||||
infra_metrics: [
|
||||
{ id: 'cpu', label: 'CPU usage' },
|
||||
{ id: 'mem', label: 'Memory usage' },
|
||||
{ id: 'nwk', label: 'Network activity' },
|
||||
|
@ -56,11 +55,13 @@ export function SeriesBuilder() {
|
|||
reportDefinitions = {},
|
||||
filters = [],
|
||||
operationType,
|
||||
breakdown,
|
||||
time,
|
||||
} = series;
|
||||
|
||||
const [isFlyoutVisible, setIsFlyoutVisible] = useState(!!series.dataType);
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern, loading, hasData } = useAppIndexPatternContext();
|
||||
|
||||
const getDataViewSeries = () => {
|
||||
return getDefaultConfigs({
|
||||
|
@ -70,19 +71,23 @@ export function SeriesBuilder() {
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsFlyoutVisible(!!series.dataType);
|
||||
}, [series.dataType]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: i18n.translate('xpack.observability.expView.seriesBuilder.dataType', {
|
||||
defaultMessage: 'Data Type',
|
||||
}),
|
||||
width: '20%',
|
||||
width: '15%',
|
||||
render: (val: string) => <DataTypesCol />,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.observability.expView.seriesBuilder.report', {
|
||||
defaultMessage: 'Report',
|
||||
}),
|
||||
width: '20%',
|
||||
width: '15%',
|
||||
render: (val: string) => (
|
||||
<ReportTypesCol reportTypes={dataType ? ReportTypes[dataType] : []} />
|
||||
),
|
||||
|
@ -92,16 +97,25 @@ export function SeriesBuilder() {
|
|||
defaultMessage: 'Definition',
|
||||
}),
|
||||
width: '30%',
|
||||
render: (val: string) =>
|
||||
reportType && indexPattern ? (
|
||||
<ReportDefinitionCol dataViewSeries={getDataViewSeries()} />
|
||||
) : null,
|
||||
render: (val: string) => {
|
||||
if (dataType && hasData) {
|
||||
return loading ? (
|
||||
INITIATING_VIEW
|
||||
) : reportType ? (
|
||||
<ReportDefinitionCol dataViewSeries={getDataViewSeries()} />
|
||||
) : (
|
||||
SELECT_REPORT_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.observability.expView.seriesBuilder.filters', {
|
||||
defaultMessage: 'Filters',
|
||||
}),
|
||||
width: '25%',
|
||||
width: '20%',
|
||||
render: (val: string) =>
|
||||
reportType && indexPattern ? <ReportFilters dataViewSeries={getDataViewSeries()} /> : null,
|
||||
},
|
||||
|
@ -109,7 +123,7 @@ export function SeriesBuilder() {
|
|||
name: i18n.translate('xpack.observability.expView.seriesBuilder.breakdown', {
|
||||
defaultMessage: 'Breakdowns',
|
||||
}),
|
||||
width: '25%',
|
||||
width: '20%',
|
||||
field: 'id',
|
||||
render: (val: string) =>
|
||||
reportType && indexPattern ? (
|
||||
|
@ -126,14 +140,15 @@ export function SeriesBuilder() {
|
|||
ReportViewTypes[reportType]
|
||||
}`;
|
||||
|
||||
const newSeriesN = {
|
||||
const newSeriesN: SeriesUrl = {
|
||||
time,
|
||||
filters,
|
||||
breakdown,
|
||||
reportType,
|
||||
seriesType,
|
||||
filters,
|
||||
reportDefinitions,
|
||||
operationType,
|
||||
time: { from: 'now-30m', to: 'now' },
|
||||
} as SeriesUrl;
|
||||
reportDefinitions,
|
||||
};
|
||||
|
||||
setSeries(newSeriesId, newSeriesN).then(() => {
|
||||
removeSeries(NEW_SERIES_KEY);
|
||||
|
@ -148,7 +163,7 @@ export function SeriesBuilder() {
|
|||
|
||||
if (isFlyoutVisible) {
|
||||
flyout = (
|
||||
<BottomFlyout aria-labelledby="flyoutTitle">
|
||||
<>
|
||||
<EuiBasicTable
|
||||
items={items as any}
|
||||
columns={columns}
|
||||
|
@ -157,7 +172,14 @@ export function SeriesBuilder() {
|
|||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill iconType="plus" color="primary" onClick={addSeries}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plus"
|
||||
color="primary"
|
||||
onClick={addSeries}
|
||||
size="s"
|
||||
isDisabled={!series?.reportType}
|
||||
>
|
||||
{i18n.translate('xpack.observability.expView.seriesBuilder.add', {
|
||||
defaultMessage: 'Add',
|
||||
})}
|
||||
|
@ -165,6 +187,7 @@ export function SeriesBuilder() {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
iconType="cross"
|
||||
color="text"
|
||||
onClick={() => {
|
||||
|
@ -178,7 +201,7 @@ export function SeriesBuilder() {
|
|||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</BottomFlyout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -205,6 +228,13 @@ export function SeriesBuilder() {
|
|||
);
|
||||
}
|
||||
|
||||
const BottomFlyout = styled.div`
|
||||
height: 300px;
|
||||
`;
|
||||
const INITIATING_VIEW = i18n.translate('xpack.observability.expView.seriesBuilder.initView', {
|
||||
defaultMessage: 'Initiating view ...',
|
||||
});
|
||||
|
||||
const SELECT_REPORT_TYPE = i18n.translate(
|
||||
'xpack.observability.expView.seriesBuilder.selectReportType',
|
||||
{
|
||||
defaultMessage: 'Select a report type to define visualization.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Props {
|
|||
series: DataSeries;
|
||||
}
|
||||
|
||||
export function ActionsCol({ series }: Props) {
|
||||
export function ChartOptions({ series }: Props) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
|
@ -13,7 +13,8 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiFilterGroup,
|
||||
} from '@elastic/eui';
|
||||
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
|
||||
import styled from 'styled-components';
|
||||
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
|
||||
import { useUrlStorage } from '../../hooks/use_url_storage';
|
||||
import { UrlFilter } from '../../types';
|
||||
import { FilterValueButton } from './filter_value_btn';
|
||||
|
@ -23,12 +24,13 @@ interface Props {
|
|||
seriesId: string;
|
||||
label: string;
|
||||
field: string;
|
||||
isNegated?: boolean;
|
||||
goBack: () => void;
|
||||
nestedField?: string;
|
||||
}
|
||||
|
||||
export function FilterExpanded({ seriesId, field, label, goBack, nestedField }: Props) {
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
export function FilterExpanded({ seriesId, field, label, goBack, nestedField, isNegated }: Props) {
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
|
@ -37,9 +39,10 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
|
|||
const { series } = useUrlStorage(seriesId);
|
||||
|
||||
const { values, loading } = useValuesList({
|
||||
query: value,
|
||||
indexPattern,
|
||||
sourceField: field,
|
||||
time: series.time,
|
||||
indexPattern,
|
||||
});
|
||||
|
||||
const filters = series?.filters ?? [];
|
||||
|
@ -51,7 +54,7 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
<EuiButtonEmpty iconType="arrowLeft" color="text" onClick={() => goBack()}>
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
|
@ -71,16 +74,18 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
|
|||
{displayValues.map((opt) => (
|
||||
<Fragment key={opt}>
|
||||
<EuiFilterGroup fullWidth={true} color="primary">
|
||||
<FilterValueButton
|
||||
field={field}
|
||||
value={opt}
|
||||
allSelectedValues={currFilter?.notValues}
|
||||
negate={true}
|
||||
nestedField={nestedField}
|
||||
seriesId={seriesId}
|
||||
isNestedOpen={isOpen}
|
||||
setIsNestedOpen={setIsOpen}
|
||||
/>
|
||||
{isNegated !== false && (
|
||||
<FilterValueButton
|
||||
field={field}
|
||||
value={opt}
|
||||
allSelectedValues={currFilter?.notValues}
|
||||
negate={true}
|
||||
nestedField={nestedField}
|
||||
seriesId={seriesId}
|
||||
isNestedOpen={isOpen}
|
||||
setIsNestedOpen={setIsOpen}
|
||||
/>
|
||||
)}
|
||||
<FilterValueButton
|
||||
field={field}
|
||||
value={opt}
|
||||
|
@ -95,6 +100,10 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField }:
|
|||
<EuiSpacer size="s" />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
max-width: 400px;
|
||||
`;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiFilterButton, hexToRgb } from '@elastic/eui';
|
||||
import { useIndexPatternContext } from '../../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
|
||||
import { useUrlStorage } from '../../hooks/use_url_storage';
|
||||
import { useSeriesFilters } from '../../hooks/use_series_filters';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
@ -39,7 +39,7 @@ export function FilterValueButton({
|
|||
}: Props) {
|
||||
const { series } = useUrlStorage(seriesId);
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
const { setFilter, removeFilter } = useSeriesFilters({ seriesId });
|
||||
|
||||
|
|
|
@ -8,18 +8,17 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
import { DataSeries } from '../../types';
|
||||
import { useUrlStorage } from '../../hooks/use_url_storage';
|
||||
|
||||
interface Props {
|
||||
series: DataSeries;
|
||||
seriesId: string;
|
||||
}
|
||||
|
||||
export function RemoveSeries({ series }: Props) {
|
||||
export function RemoveSeries({ seriesId }: Props) {
|
||||
const { removeSeries } = useUrlStorage();
|
||||
|
||||
const onClick = () => {
|
||||
removeSeries(series.id);
|
||||
removeSeries(seriesId);
|
||||
};
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { RemoveSeries } from './remove_series';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage';
|
||||
import { ReportToDataTypeMap } from '../../configurations/constants';
|
||||
|
||||
interface Props {
|
||||
seriesId: string;
|
||||
}
|
||||
export function SeriesActions({ seriesId }: Props) {
|
||||
const { series, removeSeries, setSeries } = useUrlStorage(seriesId);
|
||||
|
||||
const onEdit = () => {
|
||||
removeSeries(seriesId);
|
||||
setSeries(NEW_SERIES_KEY, { ...series, dataType: ReportToDataTypeMap[series.reportType] });
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={'documentEdit'}
|
||||
aria-label={i18n.translate('xpack.observability.seriesEditor.edit', {
|
||||
defaultMessage: 'Edit series',
|
||||
})}
|
||||
size="m"
|
||||
onClick={onEdit}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RemoveSeries seriesId={seriesId} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -32,6 +32,7 @@ export interface Field {
|
|||
label: string;
|
||||
field: string;
|
||||
nested?: string;
|
||||
isNegated?: boolean;
|
||||
}
|
||||
|
||||
export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) {
|
||||
|
@ -39,11 +40,17 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P
|
|||
|
||||
const [selectedField, setSelectedField] = useState<Field | undefined>();
|
||||
|
||||
const options = defaultFilters.map((field) => {
|
||||
const options: Field[] = defaultFilters.map((field) => {
|
||||
if (typeof field === 'string') {
|
||||
return { label: FieldLabels[field], field };
|
||||
}
|
||||
return { label: FieldLabels[field.field], field: field.field, nested: field.nested };
|
||||
|
||||
return {
|
||||
label: FieldLabels[field.field],
|
||||
field: field.field,
|
||||
nested: field.nested,
|
||||
isNegated: field.isNegated,
|
||||
};
|
||||
});
|
||||
const disabled = seriesId === NEW_SERIES_KEY && !isNew;
|
||||
|
||||
|
@ -92,6 +99,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P
|
|||
field={selectedField.field}
|
||||
label={selectedField.label}
|
||||
nestedField={selectedField.nested}
|
||||
isNegated={selectedField.isNegated}
|
||||
goBack={() => {
|
||||
setSelectedField(undefined);
|
||||
}}
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers';
|
||||
import { mockAppIndexPattern, mockIndexPattern, mockUrlStorage, render } from '../rtl_helpers';
|
||||
import { SelectedFilters } from './selected_filters';
|
||||
import { getDefaultConfigs } from '../configurations/default_configs';
|
||||
import { NEW_SERIES_KEY } from '../hooks/use_url_storage';
|
||||
import { USER_AGENT_NAME } from '../configurations/constants/elasticsearch_fieldnames';
|
||||
|
||||
describe('SelectedFilters', function () {
|
||||
mockAppIndexPattern();
|
||||
|
||||
const dataViewSeries = getDefaultConfigs({
|
||||
reportType: 'pld',
|
||||
indexPattern: mockIndexPattern,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
|
||||
import { FilterLabel } from '../components/filter_label';
|
||||
import { DataSeries, UrlFilter } from '../types';
|
||||
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
|
||||
import { useSeriesFilters } from '../hooks/use_series_filters';
|
||||
import { getFiltersFromDefs } from '../hooks/use_lens_attributes';
|
||||
|
||||
|
@ -37,7 +37,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
|
|||
|
||||
const { removeFilter } = useSeriesFilters({ seriesId });
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
return (filters.length > 0 || definitionFilters.length > 0) && indexPattern ? (
|
||||
<EuiFlexItem>
|
||||
|
@ -45,7 +45,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
|
|||
{filters.map(({ field, values, notValues }) => (
|
||||
<Fragment key={field}>
|
||||
{(values ?? []).map((val) => (
|
||||
<EuiFlexItem key={field + val} grow={false}>
|
||||
<EuiFlexItem key={field + val} grow={false} style={{ maxWidth: 300 }}>
|
||||
<FilterLabel
|
||||
seriesId={seriesId}
|
||||
field={field}
|
||||
|
@ -57,7 +57,7 @@ export function SelectedFilters({ seriesId, isNew, series: dataSeries }: Props)
|
|||
</EuiFlexItem>
|
||||
))}
|
||||
{(notValues ?? []).map((val) => (
|
||||
<EuiFlexItem key={field + val} grow={false}>
|
||||
<EuiFlexItem key={field + val} grow={false} style={{ maxWidth: 300 }}>
|
||||
<FilterLabel
|
||||
seriesId={seriesId}
|
||||
field={field}
|
||||
|
|
|
@ -8,16 +8,17 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiBasicTable, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { SeriesFilter } from './columns/series_filter';
|
||||
import { ActionsCol } from './columns/actions_col';
|
||||
import { Breakdowns } from './columns/breakdowns';
|
||||
import { DataSeries } from '../types';
|
||||
import { SeriesBuilder } from '../series_builder/series_builder';
|
||||
import { NEW_SERIES_KEY, useUrlStorage } from '../hooks/use_url_storage';
|
||||
import { getDefaultConfigs } from '../configurations/default_configs';
|
||||
import { DatePickerCol } from './columns/date_picker_col';
|
||||
import { RemoveSeries } from './columns/remove_series';
|
||||
import { useIndexPatternContext } from '../hooks/use_default_index_pattern';
|
||||
import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
|
||||
import { ChartOptions } from './columns/chart_options';
|
||||
import { SeriesActions } from './columns/series_actions';
|
||||
|
||||
export function SeriesEditor() {
|
||||
const { allSeries, firstSeriesId } = useUrlStorage();
|
||||
|
@ -32,7 +33,7 @@ export function SeriesEditor() {
|
|||
render: (val: string) => (
|
||||
<EuiText>
|
||||
<EuiIcon type="dot" color="green" size="l" />{' '}
|
||||
{val === NEW_SERIES_KEY ? 'new-series-preview' : val}
|
||||
{val === NEW_SERIES_KEY ? 'series-preview' : val}
|
||||
</EuiText>
|
||||
),
|
||||
},
|
||||
|
@ -63,34 +64,30 @@ export function SeriesEditor() {
|
|||
align: 'center' as const,
|
||||
width: '15%',
|
||||
field: 'id',
|
||||
render: (val: string, item: DataSeries) => <ActionsCol series={item} />,
|
||||
render: (val: string, item: DataSeries) => <ChartOptions series={item} />,
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.observability.expView.seriesEditor.time"
|
||||
defaultMessage="Time"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
width: '20%',
|
||||
field: 'id',
|
||||
align: 'right' as const,
|
||||
render: (val: string, item: DataSeries) => <DatePickerCol seriesId={item.id} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: (
|
||||
<div>
|
||||
{i18n.translate('xpack.observability.expView.seriesEditor.time', {
|
||||
defaultMessage: 'Time',
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
width: '20%',
|
||||
field: 'id',
|
||||
align: 'right' as const,
|
||||
render: (val: string, item: DataSeries) => <DatePickerCol seriesId={item.id} />,
|
||||
},
|
||||
|
||||
...(firstSeriesId !== NEW_SERIES_KEY
|
||||
? [
|
||||
{
|
||||
name: i18n.translate('xpack.observability.expView.seriesEditor.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
align: 'center' as const,
|
||||
width: '5%',
|
||||
width: '8%',
|
||||
field: 'id',
|
||||
render: (val: string, item: DataSeries) => <RemoveSeries series={item} />,
|
||||
render: (val: string, item: DataSeries) => <SeriesActions seriesId={item.id} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
@ -100,7 +97,7 @@ export function SeriesEditor() {
|
|||
|
||||
const items: DataSeries[] = [];
|
||||
|
||||
const { indexPattern } = useIndexPatternContext();
|
||||
const { indexPattern } = useAppIndexPatternContext();
|
||||
|
||||
allSeriesKeys.forEach((seriesKey) => {
|
||||
const series = allSeries[seriesKey];
|
||||
|
|
|
@ -57,7 +57,7 @@ export interface DataSeries {
|
|||
|
||||
breakdowns: string[];
|
||||
defaultSeriesType: SeriesType;
|
||||
defaultFilters: Array<string | { field: string; nested: string }>;
|
||||
defaultFilters: Array<string | { field: string; nested?: string; isNegated?: boolean }>;
|
||||
seriesTypes: SeriesType[];
|
||||
filters?: PersistableFilter[];
|
||||
reportDefinitions: ReportDefinition[];
|
||||
|
@ -91,7 +91,7 @@ export interface ConfigProps {
|
|||
indexPattern: IIndexPattern;
|
||||
}
|
||||
|
||||
export type AppDataType = 'synthetics' | 'rum' | 'logs' | 'metrics' | 'apm';
|
||||
export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm';
|
||||
|
||||
type FormatType = 'duration' | 'number';
|
||||
type InputFormat = 'microseconds' | 'milliseconds' | 'seconds';
|
||||
|
|
|
@ -40,17 +40,17 @@ const fieldFormats = {
|
|||
describe('ObservabilityIndexPatterns', function () {
|
||||
const { data } = mockCore();
|
||||
data!.indexPatterns.get = jest.fn().mockReturnValue({ title: 'index-*' });
|
||||
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.rum });
|
||||
data!.indexPatterns.createAndSave = jest.fn().mockReturnValue({ id: indexPatternList.ux });
|
||||
data!.indexPatterns.updateSavedObject = jest.fn();
|
||||
|
||||
it('should return index pattern for app', async function () {
|
||||
const obsv = new ObservabilityIndexPatterns(data!);
|
||||
|
||||
const indexP = await obsv.getIndexPattern('rum');
|
||||
const indexP = await obsv.getIndexPattern('ux');
|
||||
|
||||
expect(indexP).toEqual({ title: 'index-*' });
|
||||
|
||||
expect(data?.indexPatterns.get).toHaveBeenCalledWith(indexPatternList.rum);
|
||||
expect(data?.indexPatterns.get).toHaveBeenCalledWith(indexPatternList.ux);
|
||||
expect(data?.indexPatterns.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -61,9 +61,9 @@ describe('ObservabilityIndexPatterns', function () {
|
|||
|
||||
const obsv = new ObservabilityIndexPatterns(data!);
|
||||
|
||||
const indexP = await obsv.getIndexPattern('rum');
|
||||
const indexP = await obsv.getIndexPattern('ux');
|
||||
|
||||
expect(indexP).toEqual({ id: indexPatternList.rum });
|
||||
expect(indexP).toEqual({ id: indexPatternList.ux });
|
||||
|
||||
expect(data?.indexPatterns.createAndSave).toHaveBeenCalledWith({
|
||||
fieldFormats,
|
||||
|
@ -77,7 +77,7 @@ describe('ObservabilityIndexPatterns', function () {
|
|||
it('should return getFieldFormats', function () {
|
||||
const obsv = new ObservabilityIndexPatterns(data!);
|
||||
|
||||
expect(obsv.getFieldFormats('rum')).toEqual(fieldFormats);
|
||||
expect(obsv.getFieldFormats('ux')).toEqual(fieldFormats);
|
||||
});
|
||||
|
||||
it('should validate field formats', async function () {
|
||||
|
@ -85,7 +85,7 @@ describe('ObservabilityIndexPatterns', function () {
|
|||
|
||||
const obsv = new ObservabilityIndexPatterns(data!);
|
||||
|
||||
await obsv.validateFieldFormats('rum', mockIndexPattern);
|
||||
await obsv.validateFieldFormats('ux', mockIndexPattern);
|
||||
|
||||
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledTimes(1);
|
||||
expect(data?.indexPatterns.updateSavedObject).toHaveBeenCalledWith(
|
||||
|
|
|
@ -14,36 +14,35 @@ import {
|
|||
} from '../../../../../../../../src/plugins/data/public';
|
||||
import { rumFieldFormats } from '../configurations/rum/field_formats';
|
||||
import { syntheticsFieldFormats } from '../configurations/synthetics/field_formats';
|
||||
import { FieldFormat, FieldFormatParams } from '../types';
|
||||
import { AppDataType, FieldFormat, FieldFormatParams } from '../types';
|
||||
import { apmFieldFormats } from '../configurations/apm/field_formats';
|
||||
|
||||
const appFieldFormats: Record<DataType, FieldFormat[] | null> = {
|
||||
rum: rumFieldFormats,
|
||||
apm: null,
|
||||
logs: null,
|
||||
metrics: null,
|
||||
const appFieldFormats: Record<AppDataType, FieldFormat[] | null> = {
|
||||
infra_logs: null,
|
||||
infra_metrics: null,
|
||||
ux: rumFieldFormats,
|
||||
apm: apmFieldFormats,
|
||||
synthetics: syntheticsFieldFormats,
|
||||
};
|
||||
|
||||
function getFieldFormatsForApp(app: DataType) {
|
||||
function getFieldFormatsForApp(app: AppDataType) {
|
||||
return appFieldFormats[app];
|
||||
}
|
||||
|
||||
export type DataType = 'synthetics' | 'apm' | 'logs' | 'metrics' | 'rum';
|
||||
|
||||
export const indexPatternList: Record<DataType, string> = {
|
||||
export const indexPatternList: Record<AppDataType, string> = {
|
||||
synthetics: 'synthetics_static_index_pattern_id',
|
||||
apm: 'apm_static_index_pattern_id',
|
||||
rum: 'rum_static_index_pattern_id',
|
||||
logs: 'logs_static_index_pattern_id',
|
||||
metrics: 'metrics_static_index_pattern_id',
|
||||
ux: 'rum_static_index_pattern_id',
|
||||
infra_logs: 'infra_logs_static_index_pattern_id',
|
||||
infra_metrics: 'infra_metrics_static_index_pattern_id',
|
||||
};
|
||||
|
||||
const appToPatternMap: Record<DataType, string> = {
|
||||
const appToPatternMap: Record<AppDataType, string> = {
|
||||
synthetics: '(synthetics-data-view)*,heartbeat-*,synthetics-*',
|
||||
apm: 'apm-*',
|
||||
rum: '(rum-data-view)*,apm-*',
|
||||
logs: 'logs-*,filebeat-*',
|
||||
metrics: 'metrics-*,metricbeat-*',
|
||||
ux: '(rum-data-view)*,apm-*',
|
||||
infra_logs: 'logs-*,filebeat-*',
|
||||
infra_metrics: 'metrics-*,metricbeat-*',
|
||||
};
|
||||
|
||||
export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) {
|
||||
|
@ -66,7 +65,7 @@ export class ObservabilityIndexPatterns {
|
|||
this.data = data;
|
||||
}
|
||||
|
||||
async createIndexPattern(app: DataType) {
|
||||
async createIndexPattern(app: AppDataType) {
|
||||
if (!this.data) {
|
||||
throw new Error('data is not defined');
|
||||
}
|
||||
|
@ -81,7 +80,7 @@ export class ObservabilityIndexPatterns {
|
|||
});
|
||||
}
|
||||
// we want to make sure field formats remain same
|
||||
async validateFieldFormats(app: DataType, indexPattern: IndexPattern) {
|
||||
async validateFieldFormats(app: AppDataType, indexPattern: IndexPattern) {
|
||||
const defaultFieldFormats = getFieldFormatsForApp(app);
|
||||
if (defaultFieldFormats && defaultFieldFormats.length > 0) {
|
||||
let isParamsDifferent = false;
|
||||
|
@ -99,7 +98,7 @@ export class ObservabilityIndexPatterns {
|
|||
}
|
||||
}
|
||||
|
||||
getFieldFormats(app: DataType) {
|
||||
getFieldFormats(app: AppDataType) {
|
||||
const fieldFormatMap: IndexPatternSpec['fieldFormats'] = {};
|
||||
|
||||
(appFieldFormats?.[app] ?? []).forEach(({ field, format }) => {
|
||||
|
@ -109,7 +108,7 @@ export class ObservabilityIndexPatterns {
|
|||
return fieldFormatMap;
|
||||
}
|
||||
|
||||
async getIndexPattern(app: DataType): Promise<IndexPattern | undefined> {
|
||||
async getIndexPattern(app: AppDataType): Promise<IndexPattern | undefined> {
|
||||
if (!this.data) {
|
||||
throw new Error('data is not defined');
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ import { CoreStart } from 'src/core/public';
|
|||
import { text } from '@storybook/addon-knobs';
|
||||
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
|
||||
import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { FieldValueSelection, FieldValueSelectionProps } from '../field_value_selection';
|
||||
import { FieldValueSelectionProps } from '../types';
|
||||
import { FieldValueSelection } from '../field_value_selection';
|
||||
|
||||
const KibanaReactContext = createKibanaReactContext(({
|
||||
uiSettings: { get: () => {}, get$: () => new Observable() },
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FormEvent, Fragment, useEffect, useState, Dispatch, SetStateAction } from 'react';
|
||||
import React, { FormEvent, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiPopover,
|
||||
|
@ -15,20 +15,8 @@ import {
|
|||
EuiSelectableOption,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover';
|
||||
|
||||
export interface FieldValueSelectionProps {
|
||||
value?: string;
|
||||
label: string;
|
||||
loading?: boolean;
|
||||
onChange: (val?: string) => void;
|
||||
values?: string[];
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
anchorPosition?: PopoverAnchorPosition;
|
||||
forceOpen?: boolean;
|
||||
button?: JSX.Element;
|
||||
width?: number;
|
||||
}
|
||||
import styled from 'styled-components';
|
||||
import { FieldValueSelectionProps } from './types';
|
||||
|
||||
const formatOptions = (values?: string[], value?: string): EuiSelectableOption[] => {
|
||||
return (values ?? []).map((val) => ({
|
||||
|
@ -38,6 +26,7 @@ const formatOptions = (values?: string[], value?: string): EuiSelectableOption[]
|
|||
};
|
||||
|
||||
export function FieldValueSelection({
|
||||
fullWidth,
|
||||
label,
|
||||
value,
|
||||
loading,
|
||||
|
@ -47,6 +36,7 @@ export function FieldValueSelection({
|
|||
width,
|
||||
forceOpen,
|
||||
anchorPosition,
|
||||
singleSelection,
|
||||
onChange: onSelectionChange,
|
||||
}: FieldValueSelectionProps) {
|
||||
const [options, setOptions] = useState<EuiSelectableOption[]>(formatOptions(values, value));
|
||||
|
@ -81,13 +71,14 @@ export function FieldValueSelection({
|
|||
iconSide="right"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj={'fieldValueSelectionBtn'}
|
||||
fullWidth={fullWidth}
|
||||
>
|
||||
{label}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Wrapper>
|
||||
<EuiPopover
|
||||
id="popover"
|
||||
panelPaddingSize="none"
|
||||
|
@ -95,10 +86,11 @@ export function FieldValueSelection({
|
|||
isOpen={isPopoverOpen || forceOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition={anchorPosition}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<EuiSelectable
|
||||
searchable
|
||||
singleSelection
|
||||
singleSelection={singleSelection}
|
||||
searchProps={{
|
||||
placeholder: i18n.translate('xpack.observability.fieldValueSelection.placeholder', {
|
||||
defaultMessage: 'Filter {label}',
|
||||
|
@ -138,6 +130,18 @@ export function FieldValueSelection({
|
|||
)}
|
||||
</EuiSelectable>
|
||||
</EuiPopover>
|
||||
</Fragment>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
&&& {
|
||||
div.euiPopover__anchor {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
.euiButton {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -8,27 +8,12 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { useDebounce } from 'react-use';
|
||||
import { PopoverAnchorPosition } from '@elastic/eui/src/components/popover/popover';
|
||||
import { useValuesList } from '../../../hooks/use_values_list';
|
||||
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { FieldValueSelection } from './field_value_selection';
|
||||
import { ESFilter } from '../../../../../../../typings/elasticsearch';
|
||||
|
||||
export interface FieldValueSuggestionsProps {
|
||||
value?: string;
|
||||
label: string;
|
||||
indexPattern: IndexPattern;
|
||||
sourceField: string;
|
||||
onChange: (val?: string) => void;
|
||||
filters: ESFilter[];
|
||||
anchorPosition?: PopoverAnchorPosition;
|
||||
time?: { from: string; to: string };
|
||||
forceOpen?: boolean;
|
||||
button?: JSX.Element;
|
||||
width?: number;
|
||||
}
|
||||
import { FieldValueSuggestionsProps } from './types';
|
||||
|
||||
export function FieldValueSuggestions({
|
||||
fullWidth,
|
||||
sourceField,
|
||||
label,
|
||||
indexPattern,
|
||||
|
@ -39,6 +24,7 @@ export function FieldValueSuggestions({
|
|||
width,
|
||||
forceOpen,
|
||||
anchorPosition,
|
||||
singleSelection,
|
||||
onChange: onSelectionChange,
|
||||
}: FieldValueSuggestionsProps) {
|
||||
const [query, setQuery] = useState('');
|
||||
|
@ -56,6 +42,8 @@ export function FieldValueSuggestions({
|
|||
|
||||
return (
|
||||
<FieldValueSelection
|
||||
fullWidth={fullWidth}
|
||||
singleSelection={singleSelection}
|
||||
values={values as string[]}
|
||||
label={label}
|
||||
onChange={onSelectionChange}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { PopoverAnchorPosition } from '@elastic/eui';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { ESFilter } from 'typings/elasticsearch';
|
||||
import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
|
||||
|
||||
interface CommonProps {
|
||||
value?: string;
|
||||
label: string;
|
||||
button?: JSX.Element;
|
||||
width?: number;
|
||||
singleSelection?: boolean;
|
||||
forceOpen?: boolean;
|
||||
anchorPosition?: PopoverAnchorPosition;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export type FieldValueSuggestionsProps = CommonProps & {
|
||||
indexPattern: IndexPattern;
|
||||
sourceField: string;
|
||||
onChange: (val?: string) => void;
|
||||
filters: ESFilter[];
|
||||
time?: { from: string; to: string };
|
||||
};
|
||||
|
||||
export type FieldValueSelectionProps = CommonProps & {
|
||||
loading?: boolean;
|
||||
onChange: (val?: string) => void;
|
||||
values?: string[];
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
|
||||
import type { FieldValueSuggestionsProps } from './field_value_suggestions';
|
||||
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
|
||||
|
||||
const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index'));
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ export interface ObservabilityFetchDataResponse {
|
|||
apm: ApmFetchDataResponse;
|
||||
infra_metrics: MetricsFetchDataResponse;
|
||||
infra_logs: LogsFetchDataResponse;
|
||||
synthetics: UptimeFetchDataResponse;
|
||||
uptime: UptimeFetchDataResponse;
|
||||
ux: UxFetchDataResponse;
|
||||
}
|
||||
|
@ -134,5 +135,6 @@ export interface ObservabilityHasDataResponse {
|
|||
infra_metrics: boolean;
|
||||
infra_logs: boolean;
|
||||
uptime: boolean;
|
||||
synthetics: boolean;
|
||||
ux: UXHasDataResponse;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ export type ObservabilityApp =
|
|||
| 'infra_metrics'
|
||||
| 'infra_logs'
|
||||
| 'apm'
|
||||
// we will remove uptime in future to replace to be replace by synthetics
|
||||
| 'uptime'
|
||||
| 'synthetics'
|
||||
| 'observability-overview'
|
||||
| 'stack_monitoring'
|
||||
| 'ux';
|
||||
|
|
Loading…
Reference in a new issue