mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] Transforms: Adds date picker to transform wizard for data view with time fields. (#149049)
Adds a date picker to the transform wizard for data views with time fields. The time range will be applied to previews only.
This commit is contained in:
parent
ea699561f4
commit
0085aaea00
39 changed files with 933 additions and 331 deletions
|
@ -55,6 +55,7 @@ const getMockedTimefilter = () => {
|
|||
enableAutoRefreshSelector: jest.fn(),
|
||||
getRefreshInterval: jest.fn(),
|
||||
setRefreshInterval: jest.fn(),
|
||||
getActiveBounds: jest.fn(),
|
||||
getTime: jest.fn(),
|
||||
isAutoRefreshSelectorEnabled: jest.fn(),
|
||||
isTimeRangeSelectorEnabled: jest.fn(),
|
||||
|
@ -68,7 +69,7 @@ const getMockedTimefilter = () => {
|
|||
};
|
||||
};
|
||||
|
||||
const getMockedDatePickeDependencies = () => {
|
||||
const getMockedDatePickerDependencies = () => {
|
||||
return {
|
||||
data: {
|
||||
query: {
|
||||
|
@ -138,7 +139,7 @@ describe('TimeSeriesExplorerUrlStateManager', () => {
|
|||
render(
|
||||
<MlContext.Provider value={kibanaContextValueMock}>
|
||||
<I18nProvider>
|
||||
<DatePickerContextProvider {...getMockedDatePickeDependencies()}>
|
||||
<DatePickerContextProvider {...getMockedDatePickerDependencies()}>
|
||||
<TimeSeriesExplorerUrlStateManager {...props} />
|
||||
</DatePickerContextProvider>
|
||||
</I18nProvider>
|
||||
|
|
11
x-pack/plugins/transform/common/types/date_picker.ts
Normal file
11
x-pack/plugins/transform/common/types/date_picker.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface TimeRangeMs {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
|
@ -5,12 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getPreviewTransformRequestBody, SimpleQuery } from '.';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { getIndexDevConsoleStatement, getPivotPreviewDevConsoleStatement } from './data_grid';
|
||||
import { getPreviewTransformRequestBody, SimpleQuery } from '.';
|
||||
import { getIndexDevConsoleStatement, getTransformPreviewDevConsoleStatement } from './data_grid';
|
||||
|
||||
describe('Transform: Data Grid', () => {
|
||||
test('getPivotPreviewDevConsoleStatement()', () => {
|
||||
test('getTransformPreviewDevConsoleStatement()', () => {
|
||||
const query: SimpleQuery = {
|
||||
query_string: {
|
||||
query: '*',
|
||||
|
@ -18,26 +19,30 @@ describe('Transform: Data Grid', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const request = getPreviewTransformRequestBody('the-index-pattern-title', query, {
|
||||
pivot: {
|
||||
group_by: {
|
||||
'the-group-by-agg-name': {
|
||||
terms: {
|
||||
field: 'the-group-by-field',
|
||||
const request = getPreviewTransformRequestBody(
|
||||
{ getIndexPattern: () => 'the-index-pattern-title' } as DataView,
|
||||
query,
|
||||
{
|
||||
pivot: {
|
||||
group_by: {
|
||||
'the-group-by-agg-name': {
|
||||
terms: {
|
||||
field: 'the-group-by-field',
|
||||
},
|
||||
},
|
||||
},
|
||||
aggregations: {
|
||||
'the-agg-agg-name': {
|
||||
avg: {
|
||||
field: 'the-agg-field',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aggregations: {
|
||||
'the-agg-agg-name': {
|
||||
avg: {
|
||||
field: 'the-agg-field',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request);
|
||||
const pivotPreviewDevConsoleStatement = getTransformPreviewDevConsoleStatement(request);
|
||||
|
||||
expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview
|
||||
{
|
||||
|
|
|
@ -7,15 +7,17 @@
|
|||
|
||||
import type { PostTransformsPreviewRequestSchema } from '../../../common/api_schemas/transforms';
|
||||
|
||||
import { PivotQuery } from './request';
|
||||
import { TransformConfigQuery } from './request';
|
||||
|
||||
export const INIT_MAX_COLUMNS = 20;
|
||||
|
||||
export const getPivotPreviewDevConsoleStatement = (request: PostTransformsPreviewRequestSchema) => {
|
||||
export const getTransformPreviewDevConsoleStatement = (
|
||||
request: PostTransformsPreviewRequestSchema
|
||||
) => {
|
||||
return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`;
|
||||
};
|
||||
|
||||
export const getIndexDevConsoleStatement = (query: PivotQuery, dataViewTitle: string) => {
|
||||
export const getIndexDevConsoleStatement = (query: TransformConfigQuery, dataViewTitle: string) => {
|
||||
return `GET ${dataViewTitle}/_search\n${JSON.stringify(
|
||||
{
|
||||
query,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export { isAggName } from './aggregations';
|
||||
export {
|
||||
getIndexDevConsoleStatement,
|
||||
getPivotPreviewDevConsoleStatement,
|
||||
getTransformPreviewDevConsoleStatement,
|
||||
INIT_MAX_COLUMNS,
|
||||
} from './data_grid';
|
||||
export type { EsDoc, EsDocSource } from './fields';
|
||||
|
@ -64,12 +64,12 @@ export {
|
|||
pivotGroupByFieldSupport,
|
||||
PIVOT_SUPPORTED_GROUP_BY_AGGS,
|
||||
} from './pivot_group_by';
|
||||
export type { PivotQuery, SimpleQuery } from './request';
|
||||
export type { TransformConfigQuery, SimpleQuery } from './request';
|
||||
export {
|
||||
defaultQuery,
|
||||
getPreviewTransformRequestBody,
|
||||
getCreateTransformRequestBody,
|
||||
getPivotQuery,
|
||||
getTransformConfigQuery,
|
||||
getRequestPayload,
|
||||
isDefaultQuery,
|
||||
isMatchAllQuery,
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs';
|
||||
|
||||
import { PivotGroupByConfig } from '.';
|
||||
|
@ -19,19 +21,19 @@ import {
|
|||
getPreviewTransformRequestBody,
|
||||
getCreateTransformRequestBody,
|
||||
getCreateTransformSettingsRequestBody,
|
||||
getPivotQuery,
|
||||
getTransformConfigQuery,
|
||||
getMissingBucketConfig,
|
||||
getRequestPayload,
|
||||
isDefaultQuery,
|
||||
isMatchAllQuery,
|
||||
isSimpleQuery,
|
||||
matchAllQuery,
|
||||
PivotQuery,
|
||||
type TransformConfigQuery,
|
||||
} from './request';
|
||||
import { LatestFunctionConfigUI } from '../../../common/types/transform';
|
||||
import type { RuntimeField } from '@kbn/data-views-plugin/common';
|
||||
|
||||
const simpleQuery: PivotQuery = { query_string: { query: 'airline:AAL' } };
|
||||
const simpleQuery: TransformConfigQuery = { query_string: { query: 'airline:AAL' } };
|
||||
|
||||
const groupByTerms: PivotGroupByConfig = {
|
||||
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS,
|
||||
|
@ -62,12 +64,12 @@ describe('Transform: Common', () => {
|
|||
|
||||
test('isDefaultQuery()', () => {
|
||||
expect(isDefaultQuery(defaultQuery)).toBe(true);
|
||||
expect(isDefaultQuery(matchAllQuery)).toBe(false);
|
||||
expect(isDefaultQuery(matchAllQuery)).toBe(true);
|
||||
expect(isDefaultQuery(simpleQuery)).toBe(false);
|
||||
});
|
||||
|
||||
test('getPivotQuery()', () => {
|
||||
const query = getPivotQuery('the-query');
|
||||
test('getTransformConfigQuery()', () => {
|
||||
const query = getTransformConfigQuery('the-query');
|
||||
|
||||
expect(query).toEqual({
|
||||
query_string: {
|
||||
|
@ -78,14 +80,18 @@ describe('Transform: Common', () => {
|
|||
});
|
||||
|
||||
test('getPreviewTransformRequestBody()', () => {
|
||||
const query = getPivotQuery('the-query');
|
||||
const query = getTransformConfigQuery('the-query');
|
||||
|
||||
const request = getPreviewTransformRequestBody('the-data-view-title', query, {
|
||||
pivot: {
|
||||
aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
|
||||
group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
|
||||
},
|
||||
});
|
||||
const request = getPreviewTransformRequestBody(
|
||||
{ getIndexPattern: () => 'the-data-view-title' } as DataView,
|
||||
query,
|
||||
{
|
||||
pivot: {
|
||||
aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
|
||||
group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(request).toEqual({
|
||||
pivot: {
|
||||
|
@ -100,13 +106,17 @@ describe('Transform: Common', () => {
|
|||
});
|
||||
|
||||
test('getPreviewTransformRequestBody() with comma-separated index pattern', () => {
|
||||
const query = getPivotQuery('the-query');
|
||||
const request = getPreviewTransformRequestBody('the-data-view-title,the-other-title', query, {
|
||||
pivot: {
|
||||
aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
|
||||
group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
|
||||
},
|
||||
});
|
||||
const query = getTransformConfigQuery('the-query');
|
||||
const request = getPreviewTransformRequestBody(
|
||||
{ getIndexPattern: () => 'the-data-view-title,the-other-title' } as DataView,
|
||||
query,
|
||||
{
|
||||
pivot: {
|
||||
aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } },
|
||||
group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(request).toEqual({
|
||||
pivot: {
|
||||
|
@ -172,9 +182,9 @@ describe('Transform: Common', () => {
|
|||
});
|
||||
|
||||
test('getPreviewTransformRequestBody() with missing_buckets config', () => {
|
||||
const query = getPivotQuery('the-query');
|
||||
const query = getTransformConfigQuery('the-query');
|
||||
const request = getPreviewTransformRequestBody(
|
||||
'the-data-view-title',
|
||||
{ getIndexPattern: () => 'the-data-view-title' } as DataView,
|
||||
query,
|
||||
getRequestPayload([aggsAvg], [{ ...groupByTerms, ...{ missing_bucket: true } }])
|
||||
);
|
||||
|
@ -194,11 +204,12 @@ describe('Transform: Common', () => {
|
|||
});
|
||||
|
||||
test('getCreateTransformRequestBody() skips default values', () => {
|
||||
const pivotState: StepDefineExposedState = {
|
||||
const transformConfigState: StepDefineExposedState = {
|
||||
aggList: { 'the-agg-name': aggsAvg },
|
||||
groupByList: { 'the-group-by-name': groupByTerms },
|
||||
isAdvancedPivotEditorEnabled: false,
|
||||
isAdvancedSourceEditorEnabled: false,
|
||||
isDatePickerApplyEnabled: false,
|
||||
sourceConfigUpdated: false,
|
||||
searchLanguage: 'kuery',
|
||||
searchString: 'the-query',
|
||||
|
@ -239,8 +250,8 @@ describe('Transform: Common', () => {
|
|||
};
|
||||
|
||||
const request = getCreateTransformRequestBody(
|
||||
'the-data-view-title',
|
||||
pivotState,
|
||||
{ getIndexPattern: () => 'the-data-view-title' } as DataView,
|
||||
transformConfigState,
|
||||
transformDetailsState
|
||||
);
|
||||
|
||||
|
@ -278,6 +289,7 @@ describe('Transform: Common', () => {
|
|||
groupByList: { 'the-group-by-name': groupByTerms },
|
||||
isAdvancedPivotEditorEnabled: false,
|
||||
isAdvancedSourceEditorEnabled: false,
|
||||
isDatePickerApplyEnabled: false,
|
||||
sourceConfigUpdated: false,
|
||||
searchLanguage: 'kuery',
|
||||
searchString: 'the-query',
|
||||
|
@ -319,7 +331,7 @@ describe('Transform: Common', () => {
|
|||
};
|
||||
|
||||
const request = getCreateTransformRequestBody(
|
||||
'the-data-view-title',
|
||||
{ getIndexPattern: () => 'the-data-view-title' } as DataView,
|
||||
pivotState,
|
||||
transformDetailsState
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { buildBaseFilterCriteria } from '@kbn/ml-query-utils';
|
||||
|
||||
import {
|
||||
DEFAULT_CONTINUOUS_MODE_DELAY,
|
||||
|
@ -47,9 +48,15 @@ export interface SimpleQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export type PivotQuery = SimpleQuery | SavedSearchQuery;
|
||||
export interface FilterBasedSimpleQuery {
|
||||
bool: {
|
||||
filter: [SimpleQuery];
|
||||
};
|
||||
}
|
||||
|
||||
export function getPivotQuery(search: string | SavedSearchQuery): PivotQuery {
|
||||
export type TransformConfigQuery = FilterBasedSimpleQuery | SimpleQuery | SavedSearchQuery;
|
||||
|
||||
export function getTransformConfigQuery(search: string | SavedSearchQuery): TransformConfigQuery {
|
||||
if (typeof search === 'string') {
|
||||
return {
|
||||
query_string: {
|
||||
|
@ -66,6 +73,16 @@ export function isSimpleQuery(arg: unknown): arg is SimpleQuery {
|
|||
return isPopulatedObject(arg, ['query_string']);
|
||||
}
|
||||
|
||||
export function isFilterBasedSimpleQuery(arg: unknown): arg is FilterBasedSimpleQuery {
|
||||
return (
|
||||
isPopulatedObject(arg, ['bool']) &&
|
||||
isPopulatedObject(arg.bool, ['filter']) &&
|
||||
Array.isArray(arg.bool.filter) &&
|
||||
arg.bool.filter.length === 1 &&
|
||||
isSimpleQuery(arg.bool.filter[0])
|
||||
);
|
||||
}
|
||||
|
||||
export const matchAllQuery = { match_all: {} };
|
||||
export function isMatchAllQuery(query: unknown): boolean {
|
||||
return (
|
||||
|
@ -76,9 +93,14 @@ export function isMatchAllQuery(query: unknown): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
export const defaultQuery: PivotQuery = { query_string: { query: '*' } };
|
||||
export function isDefaultQuery(query: PivotQuery): boolean {
|
||||
return isSimpleQuery(query) && query.query_string.query === '*';
|
||||
export const defaultQuery: TransformConfigQuery = { query_string: { query: '*' } };
|
||||
export function isDefaultQuery(query: TransformConfigQuery): boolean {
|
||||
return (
|
||||
isMatchAllQuery(query) ||
|
||||
(isSimpleQuery(query) && query.query_string.query === '*') ||
|
||||
(isFilterBasedSimpleQuery(query) &&
|
||||
(query.bool.filter[0].query_string.query === '*' || isMatchAllQuery(query.bool.filter[0])))
|
||||
);
|
||||
}
|
||||
|
||||
export function getCombinedRuntimeMappings(
|
||||
|
@ -171,17 +193,36 @@ export const getRequestPayload = (
|
|||
};
|
||||
|
||||
export function getPreviewTransformRequestBody(
|
||||
dataViewTitle: DataView['title'],
|
||||
query: PivotQuery,
|
||||
partialRequest?: StepDefineExposedState['previewRequest'] | undefined,
|
||||
runtimeMappings?: StepDefineExposedState['runtimeMappings']
|
||||
dataView: DataView,
|
||||
transformConfigQuery: TransformConfigQuery,
|
||||
partialRequest?: StepDefineExposedState['previewRequest'],
|
||||
runtimeMappings?: StepDefineExposedState['runtimeMappings'],
|
||||
timeRangeMs?: StepDefineExposedState['timeRangeMs']
|
||||
): PostTransformsPreviewRequestSchema {
|
||||
const dataViewTitle = dataView.getIndexPattern();
|
||||
const index = dataViewTitle.split(',').map((name: string) => name.trim());
|
||||
|
||||
const hasValidTimeField = dataView.timeFieldName !== undefined && dataView.timeFieldName !== '';
|
||||
|
||||
const baseFilterCriteria = buildBaseFilterCriteria(
|
||||
dataView.timeFieldName,
|
||||
timeRangeMs?.from,
|
||||
timeRangeMs?.to,
|
||||
isDefaultQuery(transformConfigQuery) ? undefined : transformConfigQuery
|
||||
);
|
||||
|
||||
const queryWithBaseFilterCriteria = {
|
||||
bool: {
|
||||
filter: baseFilterCriteria,
|
||||
},
|
||||
};
|
||||
|
||||
const query = hasValidTimeField ? queryWithBaseFilterCriteria : transformConfigQuery;
|
||||
|
||||
return {
|
||||
source: {
|
||||
index,
|
||||
...(!isDefaultQuery(query) && !isMatchAllQuery(query) ? { query } : {}),
|
||||
...(isDefaultQuery(query) ? {} : { query }),
|
||||
...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}),
|
||||
},
|
||||
...(partialRequest ?? {}),
|
||||
|
@ -212,15 +253,18 @@ export const getCreateTransformSettingsRequestBody = (
|
|||
};
|
||||
|
||||
export const getCreateTransformRequestBody = (
|
||||
dataViewTitle: DataView['title'],
|
||||
pivotState: StepDefineExposedState,
|
||||
dataView: DataView,
|
||||
transformConfigState: StepDefineExposedState,
|
||||
transformDetailsState: StepDetailsExposedState
|
||||
): PutTransformsPivotRequestSchema | PutTransformsLatestRequestSchema => ({
|
||||
...getPreviewTransformRequestBody(
|
||||
dataViewTitle,
|
||||
getPivotQuery(pivotState.searchQuery),
|
||||
pivotState.previewRequest,
|
||||
pivotState.runtimeMappings
|
||||
dataView,
|
||||
getTransformConfigQuery(transformConfigState.searchQuery),
|
||||
transformConfigState.previewRequest,
|
||||
transformConfigState.runtimeMappings,
|
||||
transformConfigState.isDatePickerApplyEnabled && transformConfigState.timeRangeMs
|
||||
? transformConfigState.timeRangeMs
|
||||
: undefined
|
||||
),
|
||||
// conditionally add optional description
|
||||
...(transformDetailsState.transformDescription !== ''
|
||||
|
|
|
@ -10,6 +10,9 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { EuiDataGridColumn } from '@elastic/eui';
|
||||
|
||||
import { buildBaseFilterCriteria } from '@kbn/ml-query-utils';
|
||||
|
||||
import type { TimeRangeMs } from '../../../common/types/date_picker';
|
||||
import {
|
||||
isEsSearchResponse,
|
||||
isFieldHistogramsResponseSchema,
|
||||
|
@ -19,21 +22,23 @@ import {
|
|||
isKeywordDuplicate,
|
||||
removeKeywordPostfix,
|
||||
} from '../../../common/utils/field_utils';
|
||||
import { getErrorMessage } from '../../../common/utils/errors';
|
||||
import { isRuntimeMappings } from '../../../common/shared_imports';
|
||||
|
||||
import type { EsSorting, UseIndexDataReturnType } from '../../shared_imports';
|
||||
|
||||
import { getErrorMessage } from '../../../common/utils/errors';
|
||||
import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common';
|
||||
import { isDefaultQuery, matchAllQuery, TransformConfigQuery } from '../common';
|
||||
import { useAppDependencies, useToastNotifications } from '../app_dependencies';
|
||||
import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common';
|
||||
|
||||
import { SearchItems } from './use_search_items';
|
||||
import { useApi } from './use_api';
|
||||
|
||||
import { useAppDependencies, useToastNotifications } from '../app_dependencies';
|
||||
import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common';
|
||||
import { isRuntimeMappings } from '../../../common/shared_imports';
|
||||
|
||||
export const useIndexData = (
|
||||
dataView: SearchItems['dataView'],
|
||||
query: PivotQuery,
|
||||
combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings']
|
||||
query: TransformConfigQuery,
|
||||
combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'],
|
||||
timeRangeMs?: TimeRangeMs
|
||||
): UseIndexDataReturnType => {
|
||||
const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]);
|
||||
|
||||
|
@ -55,6 +60,24 @@ export const useIndexData = (
|
|||
|
||||
const [dataViewFields, setDataViewFields] = useState<string[]>();
|
||||
|
||||
const baseFilterCriteria = buildBaseFilterCriteria(
|
||||
dataView.timeFieldName,
|
||||
timeRangeMs?.from,
|
||||
timeRangeMs?.to,
|
||||
query
|
||||
);
|
||||
|
||||
const defaultQuery = useMemo(
|
||||
() => (timeRangeMs && dataView.timeFieldName ? baseFilterCriteria[0] : matchAllQuery),
|
||||
[baseFilterCriteria, dataView, timeRangeMs]
|
||||
);
|
||||
|
||||
const queryWithBaseFilterCriteria = {
|
||||
bool: {
|
||||
filter: baseFilterCriteria,
|
||||
},
|
||||
};
|
||||
|
||||
// Fetch 500 random documents to determine populated fields.
|
||||
// This is a workaround to avoid passing potentially thousands of unpopulated fields
|
||||
// (for example, as part of filebeat/metricbeat/ECS based indices)
|
||||
|
@ -70,7 +93,7 @@ export const useIndexData = (
|
|||
_source: false,
|
||||
query: {
|
||||
function_score: {
|
||||
query: { match_all: {} },
|
||||
query: defaultQuery,
|
||||
random_score: {},
|
||||
},
|
||||
},
|
||||
|
@ -106,7 +129,7 @@ export const useIndexData = (
|
|||
useEffect(() => {
|
||||
fetchDataGridSampleDocuments();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [timeRangeMs]);
|
||||
|
||||
const columns: EuiDataGridColumn[] = useMemo(() => {
|
||||
if (typeof dataViewFields === 'undefined') {
|
||||
|
@ -165,7 +188,7 @@ export const useIndexData = (
|
|||
resetPagination();
|
||||
// custom comparison
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(query)]);
|
||||
}, [JSON.stringify([query, timeRangeMs])]);
|
||||
|
||||
const fetchDataGridData = async function () {
|
||||
setErrorMessage('');
|
||||
|
@ -181,8 +204,7 @@ export const useIndexData = (
|
|||
body: {
|
||||
fields: ['*'],
|
||||
_source: false,
|
||||
// Instead of using the default query (`*`), fall back to a more efficient `match_all` query.
|
||||
query: isDefaultQuery(query) ? matchAllQuery : query,
|
||||
query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria,
|
||||
from: pagination.pageIndex * pagination.pageSize,
|
||||
size: pagination.pageSize,
|
||||
...(Object.keys(sort).length > 0 ? { sort } : {}),
|
||||
|
@ -236,7 +258,7 @@ export const useIndexData = (
|
|||
type: getFieldType(cT.schema),
|
||||
};
|
||||
}),
|
||||
isDefaultQuery(query) ? matchAllQuery : query,
|
||||
isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria,
|
||||
combinedRuntimeMappings
|
||||
);
|
||||
|
||||
|
@ -263,7 +285,14 @@ export const useIndexData = (
|
|||
}, [
|
||||
indexPattern,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
JSON.stringify([query, pagination, sortingColumns, dataViewFields, combinedRuntimeMappings]),
|
||||
JSON.stringify([
|
||||
query,
|
||||
pagination,
|
||||
sortingColumns,
|
||||
dataViewFields,
|
||||
combinedRuntimeMappings,
|
||||
timeRangeMs,
|
||||
]),
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -276,7 +305,7 @@ export const useIndexData = (
|
|||
chartsVisible,
|
||||
indexPattern,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings]),
|
||||
JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings, timeRangeMs]),
|
||||
]);
|
||||
|
||||
const renderCellValue = useRenderCellValue(dataView, pagination, tableItems);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -27,6 +27,13 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => {
|
|||
|
||||
const [searchItems, setSearchItems] = useState<SearchItems | undefined>(undefined);
|
||||
|
||||
const isMounted = useRef(true);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function fetchSavedObject(id: string) {
|
||||
let fetchedDataView;
|
||||
let fetchedSavedSearch;
|
||||
|
@ -44,7 +51,7 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => {
|
|||
spaces: appDeps.spaces,
|
||||
});
|
||||
|
||||
if (fetchedSavedSearch?.sharingSavedObjectProps?.errorJSON) {
|
||||
if (isMounted.current && fetchedSavedSearch?.sharingSavedObjectProps?.errorJSON) {
|
||||
setError(await getSavedSearchUrlConflictMessage(fetchedSavedSearch));
|
||||
return;
|
||||
}
|
||||
|
@ -52,17 +59,19 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => {
|
|||
// Just let fetchedSavedSearch stay undefined in case it doesn't exist.
|
||||
}
|
||||
|
||||
if (!isDataView(fetchedDataView) && fetchedSavedSearch === undefined) {
|
||||
setError(
|
||||
i18n.translate('xpack.transform.searchItems.errorInitializationTitle', {
|
||||
defaultMessage: `An error occurred initializing the Kibana data view or saved search.`,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isMounted.current) {
|
||||
if (!isDataView(fetchedDataView) && fetchedSavedSearch === undefined) {
|
||||
setError(
|
||||
i18n.translate('xpack.transform.searchItems.errorInitializationTitle', {
|
||||
defaultMessage: `An error occurred initializing the Kibana data view or saved search.`,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchItems(createSearchItems(fetchedDataView, fetchedSavedSearch, uiSettings));
|
||||
setError(undefined);
|
||||
setSearchItems(createSearchItems(fetchedDataView, fetchedSavedSearch, uiSettings));
|
||||
setError(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getCombinedProperties } from './use_pivot_data';
|
||||
import { getCombinedProperties } from './use_transform_config_data';
|
||||
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
||||
|
||||
describe('getCombinedProperties', () => {
|
|
@ -27,7 +27,7 @@ import {
|
|||
import { getErrorMessage } from '../../../common/utils/errors';
|
||||
|
||||
import { useAppDependencies } from '../app_dependencies';
|
||||
import { getPreviewTransformRequestBody, PivotQuery } from '../common';
|
||||
import { getPreviewTransformRequestBody, type TransformConfigQuery } from '../common';
|
||||
|
||||
import { SearchItems } from './use_search_items';
|
||||
import { useApi } from './use_api';
|
||||
|
@ -95,12 +95,13 @@ export function getCombinedProperties(
|
|||
};
|
||||
}
|
||||
|
||||
export const usePivotData = (
|
||||
dataViewTitle: SearchItems['dataView']['title'],
|
||||
query: PivotQuery,
|
||||
export const useTransformConfigData = (
|
||||
dataView: SearchItems['dataView'],
|
||||
query: TransformConfigQuery,
|
||||
validationStatus: StepDefineExposedState['validationStatus'],
|
||||
requestPayload: StepDefineExposedState['previewRequest'],
|
||||
combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings']
|
||||
combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'],
|
||||
timeRangeMs?: StepDefineExposedState['timeRangeMs']
|
||||
): UseIndexDataReturnType => {
|
||||
const [previewMappingsProperties, setPreviewMappingsProperties] =
|
||||
useState<PreviewMappingsProperties>({});
|
||||
|
@ -166,10 +167,11 @@ export const usePivotData = (
|
|||
setStatus(INDEX_STATUS.LOADING);
|
||||
|
||||
const previewRequest = getPreviewTransformRequestBody(
|
||||
dataViewTitle,
|
||||
dataView,
|
||||
query,
|
||||
requestPayload,
|
||||
combinedRuntimeMappings
|
||||
combinedRuntimeMappings,
|
||||
timeRangeMs
|
||||
);
|
||||
const resp = await api.getTransformsPreview(previewRequest);
|
||||
|
||||
|
@ -238,7 +240,10 @@ export const usePivotData = (
|
|||
getPreviewData();
|
||||
// custom comparison
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
}, [dataViewTitle, JSON.stringify([requestPayload, query, combinedRuntimeMappings])]);
|
||||
}, [
|
||||
dataView.getIndexPattern(),
|
||||
JSON.stringify([requestPayload, query, combinedRuntimeMappings, timeRangeMs]),
|
||||
]);
|
||||
|
||||
if (sortingColumns.length > 0) {
|
||||
const sortingColumnsWithTypes = sortingColumns.map((c) => ({
|
|
@ -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 React, { FC } from 'react';
|
||||
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { StepDefineFormHook } from '../step_define';
|
||||
|
||||
export const DatePickerApplySwitch: FC<StepDefineFormHook> = ({
|
||||
datePicker: {
|
||||
actions: { setDatePickerApplyEnabled },
|
||||
state: { isDatePickerApplyEnabled },
|
||||
},
|
||||
}) => {
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.transform.stepDefineForm.datePickerApplySwitchLabel', {
|
||||
defaultMessage: 'Apply time range',
|
||||
})}
|
||||
checked={isDatePickerApplyEnabled}
|
||||
onChange={() => {
|
||||
setDatePickerApplyEnabled(!isDatePickerApplyEnabled);
|
||||
}}
|
||||
data-test-subj="transformDatePickerApplySwitch"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { DatePickerApplySwitch } from './date_picker_apply_switch';
|
|
@ -21,6 +21,7 @@ export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineE
|
|||
groupByList: {} as PivotGroupByConfigDict,
|
||||
isAdvancedPivotEditorEnabled: false,
|
||||
isAdvancedSourceEditorEnabled: false,
|
||||
isDatePickerApplyEnabled: false,
|
||||
searchLanguage: QUERY_LANGUAGE_KUERY,
|
||||
searchString: undefined,
|
||||
searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch,
|
||||
|
|
|
@ -24,8 +24,8 @@ import {
|
|||
PivotConfigDefinition,
|
||||
} from '../../../../../../../common/types/transform';
|
||||
import { LatestFunctionConfig } from '../../../../../../../common/api_schemas/transforms';
|
||||
|
||||
import { RUNTIME_FIELD_TYPES } from '../../../../../../../common/shared_imports';
|
||||
import type { TimeRangeMs } from '../../../../../../../common/types/date_picker';
|
||||
|
||||
export interface ErrorMessage {
|
||||
query: string;
|
||||
|
@ -62,13 +62,15 @@ export interface StepDefineExposedState {
|
|||
sourceConfigUpdated: boolean;
|
||||
valid: boolean;
|
||||
validationStatus: { isValid: boolean; errorMessage?: string };
|
||||
runtimeMappings?: RuntimeMappings;
|
||||
runtimeMappingsUpdated: boolean;
|
||||
isRuntimeMappingsEditorEnabled: boolean;
|
||||
timeRangeMs?: TimeRangeMs;
|
||||
isDatePickerApplyEnabled: boolean;
|
||||
/**
|
||||
* Undefined when the form is incomplete or invalid
|
||||
*/
|
||||
previewRequest: { latest: LatestFunctionConfig } | { pivot: PivotConfigDefinition } | undefined;
|
||||
runtimeMappings?: RuntimeMappings;
|
||||
runtimeMappingsUpdated: boolean;
|
||||
isRuntimeMappingsEditorEnabled: boolean;
|
||||
}
|
||||
|
||||
export function isPivotPartialRequest(arg: unknown): arg is { pivot: PivotConfigDefinition } {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { merge } from 'rxjs';
|
||||
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||
|
||||
import type { TimeRangeMs } from '../../../../../../../common/types/date_picker';
|
||||
|
||||
import { StepDefineExposedState } from '../common';
|
||||
import { StepDefineFormProps } from '../step_define_form';
|
||||
|
||||
export const useDatePicker = (
|
||||
defaults: StepDefineExposedState,
|
||||
dataView: StepDefineFormProps['searchItems']['dataView']
|
||||
) => {
|
||||
const hasValidTimeField = useMemo(
|
||||
() => dataView.timeFieldName !== undefined && dataView.timeFieldName !== '',
|
||||
[dataView.timeFieldName]
|
||||
);
|
||||
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: hasValidTimeField,
|
||||
autoRefreshSelector: false,
|
||||
});
|
||||
|
||||
// The internal state of the date picker apply button.
|
||||
const [isDatePickerApplyEnabled, setDatePickerApplyEnabled] = useState(
|
||||
defaults.isDatePickerApplyEnabled
|
||||
);
|
||||
|
||||
// The time range selected via the date picker
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>();
|
||||
|
||||
// Set up subscriptions to date picker updates
|
||||
useEffect(() => {
|
||||
const updateTimeRange = () => setTimeRange(timefilter.getTime());
|
||||
|
||||
const timefilterUpdateSubscription = merge(
|
||||
timefilter.getAutoRefreshFetch$(),
|
||||
timefilter.getTimeUpdate$(),
|
||||
mlTimefilterRefresh$
|
||||
).subscribe(updateTimeRange);
|
||||
|
||||
const timefilterEnabledSubscription = timefilter
|
||||
.getEnabledUpdated$()
|
||||
.subscribe(updateTimeRange);
|
||||
|
||||
return () => {
|
||||
timefilterUpdateSubscription.unsubscribe();
|
||||
timefilterEnabledSubscription.unsubscribe();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Derive ms timestamps from timeRange updates.
|
||||
const timeRangeMs: TimeRangeMs | undefined = useMemo(() => {
|
||||
const timefilterActiveBounds = timefilter.getActiveBounds();
|
||||
if (
|
||||
timefilterActiveBounds !== undefined &&
|
||||
timefilterActiveBounds.min !== undefined &&
|
||||
timefilterActiveBounds.max !== undefined
|
||||
) {
|
||||
return {
|
||||
from: timefilterActiveBounds.min.valueOf(),
|
||||
to: timefilterActiveBounds.max.valueOf(),
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timeRange]);
|
||||
|
||||
return {
|
||||
actions: { setDatePickerApplyEnabled },
|
||||
state: { isDatePickerApplyEnabled, hasValidTimeField, timeRange, timeRangeMs },
|
||||
};
|
||||
};
|
|
@ -10,7 +10,7 @@ import { useState } from 'react';
|
|||
import { toElasticsearchQuery, fromKueryExpression, luceneStringToDsl } from '@kbn/es-query';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
|
||||
import { getPivotQuery } from '../../../../../common';
|
||||
import { getTransformConfigQuery } from '../../../../../common';
|
||||
|
||||
import {
|
||||
ErrorMessage,
|
||||
|
@ -65,7 +65,7 @@ export const useSearchBar = (
|
|||
}
|
||||
};
|
||||
|
||||
const pivotQuery = getPivotQuery(searchQuery);
|
||||
const transformConfigQuery = getTransformConfigQuery(searchQuery);
|
||||
|
||||
return {
|
||||
actions: {
|
||||
|
@ -79,7 +79,7 @@ export const useSearchBar = (
|
|||
},
|
||||
state: {
|
||||
errorMessage,
|
||||
pivotQuery,
|
||||
transformConfigQuery,
|
||||
searchInput,
|
||||
searchLanguage,
|
||||
searchQuery,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { StepDefineFormProps } from '../step_define_form';
|
|||
|
||||
import { useAdvancedPivotEditor } from './use_advanced_pivot_editor';
|
||||
import { useAdvancedSourceEditor } from './use_advanced_source_editor';
|
||||
import { useDatePicker } from './use_date_picker';
|
||||
import { usePivotConfig } from './use_pivot_config';
|
||||
import { useSearchBar } from './use_search_bar';
|
||||
import { useLatestFunctionConfig } from './use_latest_function_config';
|
||||
|
@ -29,6 +30,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
|
||||
const [transformFunction, setTransformFunction] = useState(defaults.transformFunction);
|
||||
|
||||
const datePicker = useDatePicker(defaults, dataView);
|
||||
const searchBar = useSearchBar(defaults, dataView);
|
||||
const pivotConfig = usePivotConfig(defaults, dataView);
|
||||
|
||||
|
@ -39,8 +41,8 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
);
|
||||
|
||||
const previewRequest = getPreviewTransformRequestBody(
|
||||
dataView.getIndexPattern(),
|
||||
searchBar.state.pivotQuery,
|
||||
dataView,
|
||||
searchBar.state.transformConfigQuery,
|
||||
pivotConfig.state.requestPayload,
|
||||
defaults?.runtimeMappings
|
||||
);
|
||||
|
@ -58,8 +60,8 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
const runtimeMappings = runtimeMappingsEditor.state.runtimeMappings;
|
||||
if (!advancedSourceEditor.state.isAdvancedSourceEditorEnabled) {
|
||||
const previewRequestUpdate = getPreviewTransformRequestBody(
|
||||
dataView.getIndexPattern(),
|
||||
searchBar.state.pivotQuery,
|
||||
dataView,
|
||||
searchBar.state.transformConfigQuery,
|
||||
pivotConfig.state.requestPayload,
|
||||
runtimeMappings
|
||||
);
|
||||
|
@ -79,6 +81,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
groupByList: pivotConfig.state.groupByList,
|
||||
isAdvancedPivotEditorEnabled: advancedPivotEditor.state.isAdvancedPivotEditorEnabled,
|
||||
isAdvancedSourceEditorEnabled: advancedSourceEditor.state.isAdvancedSourceEditorEnabled,
|
||||
isDatePickerApplyEnabled: datePicker.state.isDatePickerApplyEnabled,
|
||||
searchLanguage: searchBar.state.searchLanguage,
|
||||
searchString: searchBar.state.searchString,
|
||||
searchQuery: searchBar.state.searchQuery,
|
||||
|
@ -98,12 +101,14 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
runtimeMappings,
|
||||
runtimeMappingsUpdated: runtimeMappingsEditor.state.runtimeMappingsUpdated,
|
||||
isRuntimeMappingsEditorEnabled: runtimeMappingsEditor.state.isRuntimeMappingsEditorEnabled,
|
||||
timeRangeMs: datePicker.state.timeRangeMs,
|
||||
});
|
||||
// custom comparison
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
}, [
|
||||
JSON.stringify(advancedPivotEditor.state),
|
||||
JSON.stringify(advancedSourceEditor.state),
|
||||
JSON.stringify(datePicker.state),
|
||||
pivotConfig.state,
|
||||
JSON.stringify(searchBar.state),
|
||||
JSON.stringify([
|
||||
|
@ -121,6 +126,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi
|
|||
advancedPivotEditor,
|
||||
advancedSourceEditor,
|
||||
runtimeMappingsEditor,
|
||||
datePicker,
|
||||
pivotConfig,
|
||||
latestFunctionConfig,
|
||||
searchBar,
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useDocumentationLinks } from '../../../../hooks/use_documentation_links';
|
||||
|
||||
import { AdvancedPivotEditor } from '../advanced_pivot_editor';
|
||||
import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch';
|
||||
import { PivotConfiguration } from '../pivot_configuration';
|
||||
|
||||
import type { StepDefineFormHook } from './hooks/use_step_define_form';
|
||||
|
||||
const advancedEditorsSidebarWidth = '220px';
|
||||
|
||||
interface PivotFunctionFormProps {
|
||||
applyPivotChangesHandler: () => void;
|
||||
copyToClipboardPivot: string;
|
||||
copyToClipboardPivotDescription: string;
|
||||
stepDefineForm: StepDefineFormHook;
|
||||
}
|
||||
|
||||
export const PivotFunctionForm: FC<PivotFunctionFormProps> = ({
|
||||
applyPivotChangesHandler,
|
||||
copyToClipboardPivot,
|
||||
copyToClipboardPivotDescription,
|
||||
stepDefineForm,
|
||||
}) => {
|
||||
const { esTransformPivot } = useDocumentationLinks();
|
||||
|
||||
const { isAdvancedPivotEditorEnabled, isAdvancedPivotEditorApplyButtonEnabled } =
|
||||
stepDefineForm.advancedPivotEditor.state;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{/* Flex Column #1: Pivot Config Form / Advanced Pivot Config Editor */}
|
||||
<EuiFlexItem>
|
||||
{!isAdvancedPivotEditorEnabled && <PivotConfiguration {...stepDefineForm.pivotConfig} />}
|
||||
{isAdvancedPivotEditorEnabled && (
|
||||
<AdvancedPivotEditor {...stepDefineForm.advancedPivotEditor} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ width: advancedEditorsSidebarWidth }}>
|
||||
<EuiFlexGroup gutterSize="xs" direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow hasEmptyLabelSpace>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AdvancedPivotEditorSwitch {...stepDefineForm} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCopy
|
||||
beforeMessage={copyToClipboardPivotDescription}
|
||||
textToCopy={copyToClipboardPivot}
|
||||
>
|
||||
{(copy: () => void) => (
|
||||
<EuiButtonIcon
|
||||
onClick={copy}
|
||||
iconType="copyClipboard"
|
||||
aria-label={copyToClipboardPivotDescription}
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{isAdvancedPivotEditorEnabled && (
|
||||
<EuiFlexItem style={{ width: advancedEditorsSidebarWidth }}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs">
|
||||
<>
|
||||
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', {
|
||||
defaultMessage:
|
||||
'The advanced editor allows you to edit the pivot configuration of the transform.',
|
||||
})}{' '}
|
||||
<EuiLink href={esTransformPivot} target="_blank">
|
||||
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', {
|
||||
defaultMessage: 'Learn more about available options.',
|
||||
})}
|
||||
</EuiLink>
|
||||
</>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton
|
||||
style={{ width: 'fit-content' }}
|
||||
size="s"
|
||||
fill
|
||||
onClick={applyPivotChangesHandler}
|
||||
disabled={!isAdvancedPivotEditorApplyButtonEnabled}
|
||||
>
|
||||
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorApplyButtonText', {
|
||||
defaultMessage: 'Apply changes',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -9,12 +9,11 @@ import React from 'react';
|
|||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
const startMock = coreMock.createStart();
|
||||
import { timefilterServiceMock } from '@kbn/data-plugin/public/query/timefilter/timefilter_service.mock';
|
||||
|
||||
import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs';
|
||||
|
||||
|
@ -28,11 +27,24 @@ import { SearchItems } from '../../../../hooks/use_search_items';
|
|||
import { getAggNameConflictToastMessages } from './common';
|
||||
import { StepDefineForm } from './step_define_form';
|
||||
|
||||
import { MlSharedContext } from '../../../../__mocks__/shared_context';
|
||||
import { getMlSharedImports } from '../../../../../shared_imports';
|
||||
|
||||
jest.mock('../../../../../shared_imports');
|
||||
jest.mock('../../../../app_dependencies');
|
||||
|
||||
import { MlSharedContext } from '../../../../__mocks__/shared_context';
|
||||
import { getMlSharedImports } from '../../../../../shared_imports';
|
||||
const startMock = coreMock.createStart();
|
||||
|
||||
const getMockedDatePickerDependencies = () => {
|
||||
return {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: timefilterServiceMock.createStartContract(),
|
||||
},
|
||||
},
|
||||
notifications: {},
|
||||
} as unknown as DatePickerDependencies;
|
||||
};
|
||||
|
||||
const createMockWebStorage = () => ({
|
||||
clear: jest.fn(),
|
||||
|
@ -75,7 +87,9 @@ describe('Transform: <DefinePivotForm />', () => {
|
|||
<I18nProvider>
|
||||
<KibanaContextProvider services={services}>
|
||||
<MlSharedContext.Provider value={mlSharedImports}>
|
||||
<StepDefineForm onChange={jest.fn()} searchItems={searchItems as SearchItems} />
|
||||
<DatePickerContextProvider {...getMockedDatePickerDependencies()}>
|
||||
<StepDefineForm onChange={jest.fn()} searchItems={searchItems as SearchItems} />
|
||||
</DatePickerContextProvider>
|
||||
</MlSharedContext.Provider>
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, FC } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useMemo, FC } from 'react';
|
||||
import { merge } from 'rxjs';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -17,20 +16,25 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiHorizontalRule,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { mlTimefilterRefresh$, useTimefilter, DatePickerWrapper } from '@kbn/ml-date-picker';
|
||||
import { useUrlState } from '@kbn/ml-url-state';
|
||||
|
||||
import { PivotAggDict } from '../../../../../../common/types/pivot_aggs';
|
||||
import { PivotGroupByDict } from '../../../../../../common/types/pivot_group_by';
|
||||
import { TRANSFORM_FUNCTION } from '../../../../../../common/constants';
|
||||
|
||||
import {
|
||||
getIndexDevConsoleStatement,
|
||||
getPivotPreviewDevConsoleStatement,
|
||||
getTransformPreviewDevConsoleStatement,
|
||||
} from '../../../../common/data_grid';
|
||||
|
||||
import {
|
||||
getPreviewTransformRequestBody,
|
||||
PivotAggsConfigDict,
|
||||
|
@ -40,24 +44,36 @@ import {
|
|||
} from '../../../../common';
|
||||
import { useDocumentationLinks } from '../../../../hooks/use_documentation_links';
|
||||
import { useIndexData } from '../../../../hooks/use_index_data';
|
||||
import { usePivotData } from '../../../../hooks/use_pivot_data';
|
||||
import { useTransformConfigData } from '../../../../hooks/use_transform_config_data';
|
||||
import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
|
||||
import { SearchItems } from '../../../../hooks/use_search_items';
|
||||
import { getAggConfigFromEsAgg } from '../../../../common/pivot_aggs';
|
||||
|
||||
import { AdvancedPivotEditor } from '../advanced_pivot_editor';
|
||||
import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch';
|
||||
import { AdvancedQueryEditorSwitch } from '../advanced_query_editor_switch';
|
||||
import { AdvancedSourceEditor } from '../advanced_source_editor';
|
||||
import { PivotConfiguration } from '../pivot_configuration';
|
||||
import { DatePickerApplySwitch } from '../date_picker_apply_switch';
|
||||
import { SourceSearchBar } from '../source_search_bar';
|
||||
import { AdvancedRuntimeMappingsSettings } from '../advanced_runtime_mappings_settings';
|
||||
|
||||
import { StepDefineExposedState } from './common';
|
||||
import { useStepDefineForm } from './hooks/use_step_define_form';
|
||||
import { getAggConfigFromEsAgg } from '../../../../common/pivot_aggs';
|
||||
import { TransformFunctionSelector } from './transform_function_selector';
|
||||
import { TRANSFORM_FUNCTION } from '../../../../../../common/constants';
|
||||
import { LatestFunctionForm } from './latest_function_form';
|
||||
import { AdvancedRuntimeMappingsSettings } from '../advanced_runtime_mappings_settings';
|
||||
import { PivotFunctionForm } from './pivot_function_form';
|
||||
|
||||
const ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG = false;
|
||||
|
||||
const advancedEditorsSidebarWidth = '220px';
|
||||
|
||||
export const ConfigSectionTitle: FC<{ title: string }> = ({ title }) => (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiTitle size="xs">
|
||||
<span>{title}</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
|
||||
export interface StepDefineFormProps {
|
||||
overrides?: StepDefineExposedState;
|
||||
|
@ -66,6 +82,7 @@ export interface StepDefineFormProps {
|
|||
}
|
||||
|
||||
export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
||||
const [globalState, setGlobalState] = useUrlState('_g');
|
||||
const { searchItems } = props;
|
||||
const { dataView } = searchItems;
|
||||
const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]);
|
||||
|
@ -75,24 +92,18 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
const toastNotifications = useToastNotifications();
|
||||
const stepDefineForm = useStepDefineForm(props);
|
||||
|
||||
const {
|
||||
advancedEditorConfig,
|
||||
isAdvancedPivotEditorEnabled,
|
||||
isAdvancedPivotEditorApplyButtonEnabled,
|
||||
} = stepDefineForm.advancedPivotEditor.state;
|
||||
const { advancedEditorConfig } = stepDefineForm.advancedPivotEditor.state;
|
||||
const {
|
||||
advancedEditorSourceConfig,
|
||||
isAdvancedSourceEditorEnabled,
|
||||
isAdvancedSourceEditorApplyButtonEnabled,
|
||||
} = stepDefineForm.advancedSourceEditor.state;
|
||||
const pivotQuery = stepDefineForm.searchBar.state.pivotQuery;
|
||||
const { isDatePickerApplyEnabled, timeRangeMs } = stepDefineForm.datePicker.state;
|
||||
const { transformConfigQuery } = stepDefineForm.searchBar.state;
|
||||
const { runtimeMappings } = stepDefineForm.runtimeMappingsEditor.state;
|
||||
|
||||
const indexPreviewProps = {
|
||||
...useIndexData(
|
||||
dataView,
|
||||
stepDefineForm.searchBar.state.pivotQuery,
|
||||
stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
|
||||
),
|
||||
...useIndexData(dataView, transformConfigQuery, runtimeMappings, timeRangeMs),
|
||||
dataTestSubj: 'transformIndexPreview',
|
||||
toastNotifications,
|
||||
};
|
||||
|
@ -101,16 +112,7 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
? stepDefineForm.pivotConfig.state
|
||||
: stepDefineForm.latestFunctionConfig;
|
||||
|
||||
const previewRequest = getPreviewTransformRequestBody(
|
||||
indexPattern,
|
||||
pivotQuery,
|
||||
stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT
|
||||
? stepDefineForm.pivotConfig.state.requestPayload
|
||||
: stepDefineForm.latestFunctionConfig.requestPayload,
|
||||
stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
|
||||
);
|
||||
|
||||
const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern);
|
||||
const copyToClipboardSource = getIndexDevConsoleStatement(transformConfigQuery, indexPattern);
|
||||
const copyToClipboardSourceDescription = i18n.translate(
|
||||
'xpack.transform.indexPreview.copyClipboardTooltip',
|
||||
{
|
||||
|
@ -118,7 +120,17 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
}
|
||||
);
|
||||
|
||||
const copyToClipboardPivot = getPivotPreviewDevConsoleStatement(previewRequest);
|
||||
const copyToClipboardPreviewRequest = getPreviewTransformRequestBody(
|
||||
dataView,
|
||||
transformConfigQuery,
|
||||
requestPayload,
|
||||
runtimeMappings,
|
||||
isDatePickerApplyEnabled ? timeRangeMs : undefined
|
||||
);
|
||||
|
||||
const copyToClipboardPivot = getTransformPreviewDevConsoleStatement(
|
||||
copyToClipboardPreviewRequest
|
||||
);
|
||||
const copyToClipboardPivotDescription = i18n.translate(
|
||||
'xpack.transform.pivotPreview.copyClipboardTooltip',
|
||||
{
|
||||
|
@ -126,18 +138,16 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
}
|
||||
);
|
||||
|
||||
const pivotPreviewProps = {
|
||||
...usePivotData(
|
||||
indexPattern,
|
||||
pivotQuery,
|
||||
const previewProps = {
|
||||
...useTransformConfigData(
|
||||
dataView,
|
||||
transformConfigQuery,
|
||||
validationStatus,
|
||||
requestPayload,
|
||||
stepDefineForm.runtimeMappingsEditor.state.runtimeMappings
|
||||
runtimeMappings,
|
||||
timeRangeMs
|
||||
),
|
||||
dataTestSubj: 'transformPivotPreview',
|
||||
title: i18n.translate('xpack.transform.pivotPreview.transformPreviewTitle', {
|
||||
defaultMessage: 'Transform preview',
|
||||
}),
|
||||
toastNotifications,
|
||||
...(stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST
|
||||
? {
|
||||
|
@ -192,9 +202,52 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
stepDefineForm.advancedPivotEditor.actions.setAdvancedPivotEditorApplyButtonEnabled(false);
|
||||
};
|
||||
|
||||
const { esQueryDsl, esTransformPivot } = useDocumentationLinks();
|
||||
const { esQueryDsl } = useDocumentationLinks();
|
||||
|
||||
const advancedEditorsSidebarWidth = '220px';
|
||||
const hasValidTimeField = useMemo(
|
||||
() => dataView.timeFieldName !== undefined && dataView.timeFieldName !== '',
|
||||
[dataView.timeFieldName]
|
||||
);
|
||||
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: dataView?.timeFieldName !== undefined,
|
||||
autoRefreshSelector: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.time !== undefined) {
|
||||
timefilter.setTime({
|
||||
from: globalState.time.from,
|
||||
to: globalState.time.to,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.time), timefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.refreshInterval !== undefined) {
|
||||
timefilter.setRefreshInterval(globalState.refreshInterval);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.refreshInterval), timefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
const timeUpdateSubscription = merge(
|
||||
timefilter.getAutoRefreshFetch$(),
|
||||
timefilter.getTimeUpdate$(),
|
||||
mlTimefilterRefresh$
|
||||
).subscribe(() => {
|
||||
if (setGlobalState) {
|
||||
setGlobalState({
|
||||
time: timefilter.getTime(),
|
||||
refreshInterval: timefilter.getRefreshInterval(),
|
||||
});
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
timeUpdateSubscription.unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-test-subj="transformStepDefineForm">
|
||||
|
@ -206,6 +259,8 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<ConfigSectionTitle title="Source data" />
|
||||
|
||||
{searchItems.savedSearch === undefined && (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.transform.stepDefineForm.dataViewLabel', {
|
||||
|
@ -216,15 +271,61 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
</EuiFormRow>
|
||||
)}
|
||||
|
||||
{hasValidTimeField && (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<>
|
||||
{i18n.translate('xpack.transform.stepDefineForm.datePickerLabel', {
|
||||
defaultMessage: 'Time range',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.transform.stepDefineForm.datePickerIconTipContent',
|
||||
{
|
||||
defaultMessage:
|
||||
'The time range will be applied to previews only and will not be part of the final transform configuration.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup alignItems="flexStart" justifyContent="spaceBetween">
|
||||
{/* Flex Column #1: Date Picker */}
|
||||
<EuiFlexItem>
|
||||
<DatePickerWrapper
|
||||
isAutoRefreshOnly={!hasValidTimeField}
|
||||
showRefresh={!hasValidTimeField}
|
||||
width="full"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{/* Flex Column #2: Apply-To-Config option */}
|
||||
<EuiFlexItem grow={false} style={{ width: advancedEditorsSidebarWidth }}>
|
||||
{ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG && (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
{searchItems.savedSearch === undefined && (
|
||||
<DatePickerApplySwitch {...stepDefineForm} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
hasEmptyLabelSpace={searchItems?.savedSearch?.id === undefined}
|
||||
label={
|
||||
searchItems?.savedSearch?.id !== undefined
|
||||
? i18n.translate('xpack.transform.stepDefineForm.savedSearchLabel', {
|
||||
defaultMessage: 'Saved search',
|
||||
})
|
||||
: ''
|
||||
: i18n.translate('xpack.transform.stepDefineForm.searchFilterLabel', {
|
||||
defaultMessage: 'Search filter',
|
||||
})
|
||||
}
|
||||
>
|
||||
<>
|
||||
|
@ -314,87 +415,30 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
<AdvancedRuntimeMappingsSettings {...stepDefineForm} />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<DataGrid {...indexPreviewProps} />
|
||||
<EuiFormRow
|
||||
fullWidth={true}
|
||||
label={i18n.translate('xpack.transform.stepDefineForm.dataGridLabel', {
|
||||
defaultMessage: 'Source documents',
|
||||
})}
|
||||
>
|
||||
<DataGrid {...indexPreviewProps} />
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
||||
<ConfigSectionTitle title="Transform configuration" />
|
||||
|
||||
<EuiForm>
|
||||
{stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{/* Flex Column #1: Pivot Config Form / Advanced Pivot Config Editor */}
|
||||
<EuiFlexItem>
|
||||
{!isAdvancedPivotEditorEnabled && (
|
||||
<PivotConfiguration {...stepDefineForm.pivotConfig} />
|
||||
)}
|
||||
{isAdvancedPivotEditorEnabled && (
|
||||
<AdvancedPivotEditor {...stepDefineForm.advancedPivotEditor} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ width: advancedEditorsSidebarWidth }}>
|
||||
<EuiFlexGroup gutterSize="xs" direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow hasEmptyLabelSpace>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AdvancedPivotEditorSwitch {...stepDefineForm} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCopy
|
||||
beforeMessage={copyToClipboardPivotDescription}
|
||||
textToCopy={copyToClipboardPivot}
|
||||
>
|
||||
{(copy: () => void) => (
|
||||
<EuiButtonIcon
|
||||
onClick={copy}
|
||||
iconType="copyClipboard"
|
||||
aria-label={copyToClipboardPivotDescription}
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{isAdvancedPivotEditorEnabled && (
|
||||
<EuiFlexItem style={{ width: advancedEditorsSidebarWidth }}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs">
|
||||
<>
|
||||
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', {
|
||||
defaultMessage:
|
||||
'The advanced editor allows you to edit the pivot configuration of the transform.',
|
||||
})}{' '}
|
||||
<EuiLink href={esTransformPivot} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.transform.stepDefineForm.advancedEditorHelpTextLink',
|
||||
{
|
||||
defaultMessage: 'Learn more about available options.',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
</>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton
|
||||
style={{ width: 'fit-content' }}
|
||||
size="s"
|
||||
fill
|
||||
onClick={applyPivotChangesHandler}
|
||||
disabled={!isAdvancedPivotEditorApplyButtonEnabled}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.transform.stepDefineForm.advancedEditorApplyButtonText',
|
||||
{
|
||||
defaultMessage: 'Apply changes',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<PivotFunctionForm
|
||||
{...{
|
||||
applyPivotChangesHandler,
|
||||
copyToClipboardPivot,
|
||||
copyToClipboardPivotDescription,
|
||||
stepDefineForm,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? (
|
||||
<LatestFunctionForm
|
||||
|
@ -407,10 +451,17 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
|
|||
<EuiSpacer size="m" />
|
||||
{(stepDefineForm.transformFunction !== TRANSFORM_FUNCTION.LATEST ||
|
||||
stepDefineForm.latestFunctionConfig.sortFieldOptions.length > 0) && (
|
||||
<>
|
||||
<DataGrid {...pivotPreviewProps} />
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.transform.stepDefineForm.previewLabel', {
|
||||
defaultMessage: 'Preview',
|
||||
})}
|
||||
>
|
||||
<>
|
||||
<DataGrid {...previewProps} />
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,13 +14,13 @@ import { EuiBadge, EuiCodeBlock, EuiForm, EuiFormRow, EuiSpacer, EuiText } from
|
|||
|
||||
import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
|
||||
import {
|
||||
getPivotQuery,
|
||||
getPivotPreviewDevConsoleStatement,
|
||||
getTransformConfigQuery,
|
||||
getTransformPreviewDevConsoleStatement,
|
||||
getPreviewTransformRequestBody,
|
||||
isDefaultQuery,
|
||||
isMatchAllQuery,
|
||||
} from '../../../../common';
|
||||
import { usePivotData } from '../../../../hooks/use_pivot_data';
|
||||
import { useTransformConfigData } from '../../../../hooks/use_transform_config_data';
|
||||
import { SearchItems } from '../../../../hooks/use_search_items';
|
||||
|
||||
import { AggListSummary } from '../aggregation_list';
|
||||
|
@ -37,6 +37,8 @@ interface Props {
|
|||
|
||||
export const StepDefineSummary: FC<Props> = ({
|
||||
formState: {
|
||||
isDatePickerApplyEnabled,
|
||||
timeRangeMs,
|
||||
runtimeMappings,
|
||||
searchString,
|
||||
searchQuery,
|
||||
|
@ -49,31 +51,33 @@ export const StepDefineSummary: FC<Props> = ({
|
|||
searchItems,
|
||||
}) => {
|
||||
const {
|
||||
ml: { DataGrid },
|
||||
ml: { formatHumanReadableDateTimeSeconds, DataGrid },
|
||||
} = useAppDependencies();
|
||||
const toastNotifications = useToastNotifications();
|
||||
|
||||
const pivotQuery = getPivotQuery(searchQuery);
|
||||
const transformConfigQuery = getTransformConfigQuery(searchQuery);
|
||||
|
||||
const previewRequest = getPreviewTransformRequestBody(
|
||||
searchItems.dataView.getIndexPattern(),
|
||||
pivotQuery,
|
||||
searchItems.dataView,
|
||||
transformConfigQuery,
|
||||
partialPreviewRequest,
|
||||
runtimeMappings
|
||||
runtimeMappings,
|
||||
isDatePickerApplyEnabled ? timeRangeMs : undefined
|
||||
);
|
||||
|
||||
const pivotPreviewProps = usePivotData(
|
||||
searchItems.dataView.getIndexPattern(),
|
||||
pivotQuery,
|
||||
const pivotPreviewProps = useTransformConfigData(
|
||||
searchItems.dataView,
|
||||
transformConfigQuery,
|
||||
validationStatus,
|
||||
partialPreviewRequest,
|
||||
runtimeMappings
|
||||
runtimeMappings,
|
||||
isDatePickerApplyEnabled ? timeRangeMs : undefined
|
||||
);
|
||||
|
||||
const isModifiedQuery =
|
||||
typeof searchString === 'undefined' &&
|
||||
!isDefaultQuery(pivotQuery) &&
|
||||
!isMatchAllQuery(pivotQuery);
|
||||
!isDefaultQuery(transformConfigQuery) &&
|
||||
!isMatchAllQuery(transformConfigQuery);
|
||||
|
||||
let uniqueKeys: string[] = [];
|
||||
let sortField = '';
|
||||
|
@ -94,6 +98,18 @@ export const StepDefineSummary: FC<Props> = ({
|
|||
>
|
||||
<span>{searchItems.dataView.getIndexPattern()}</span>
|
||||
</EuiFormRow>
|
||||
{isDatePickerApplyEnabled && timeRangeMs && (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.transform.stepDefineSummary.timeRangeLabel', {
|
||||
defaultMessage: 'Time range',
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
{formatHumanReadableDateTimeSeconds(timeRangeMs.from)} -{' '}
|
||||
{formatHumanReadableDateTimeSeconds(timeRangeMs.to)}
|
||||
</span>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{typeof searchString === 'string' && (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.transform.stepDefineSummary.queryLabel', {
|
||||
|
@ -117,7 +133,7 @@ export const StepDefineSummary: FC<Props> = ({
|
|||
overflowHeight={300}
|
||||
isCopyable
|
||||
>
|
||||
{JSON.stringify(pivotQuery, null, 2)}
|
||||
{JSON.stringify(transformConfigQuery, null, 2)}
|
||||
</EuiCodeBlock>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
|
@ -187,7 +203,7 @@ export const StepDefineSummary: FC<Props> = ({
|
|||
<EuiSpacer size="m" />
|
||||
<DataGrid
|
||||
{...pivotPreviewProps}
|
||||
copyToClipboard={getPivotPreviewDevConsoleStatement(previewRequest)}
|
||||
copyToClipboard={getTransformPreviewDevConsoleStatement(previewRequest)}
|
||||
copyToClipboardDescription={i18n.translate(
|
||||
'xpack.transform.pivotPreview.copyClipboardTooltip',
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ import { SearchItems } from '../../../../hooks/use_search_items';
|
|||
import { useApi } from '../../../../hooks/use_api';
|
||||
import { StepDetailsTimeField } from './step_details_time_field';
|
||||
import {
|
||||
getPivotQuery,
|
||||
getTransformConfigQuery,
|
||||
getPreviewTransformRequestBody,
|
||||
isTransformIdValid,
|
||||
} from '../../../../common';
|
||||
|
@ -132,10 +132,10 @@ export const StepDetailsForm: FC<StepDetailsFormProps> = React.memo(
|
|||
// use an IIFE to avoid returning a Promise to useEffect.
|
||||
(async function () {
|
||||
const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState;
|
||||
const pivotQuery = getPivotQuery(searchQuery);
|
||||
const transformConfigQuery = getTransformConfigQuery(searchQuery);
|
||||
const previewRequest = getPreviewTransformRequestBody(
|
||||
searchItems.dataView.getIndexPattern(),
|
||||
pivotQuery,
|
||||
searchItems.dataView,
|
||||
transformConfigQuery,
|
||||
partialPreviewRequest,
|
||||
stepDefineState.runtimeMappings
|
||||
);
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { type FrozenTierPreference } from '@kbn/ml-date-picker';
|
||||
|
||||
export const TRANSFORM_FROZEN_TIER_PREFERENCE = 'transform.frozenDataTierPreference';
|
||||
|
||||
export type Transform = Partial<{
|
||||
[TRANSFORM_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
||||
}> | null;
|
||||
|
||||
export type TransformKey = keyof Exclude<Transform, null>;
|
||||
|
||||
export type TransformStorageMapped<T extends TransformKey> =
|
||||
T extends typeof TRANSFORM_FROZEN_TIER_PREFERENCE ? FrozenTierPreference | undefined : null;
|
||||
|
||||
export const TRANSFORM_STORAGE_KEYS = [TRANSFORM_FROZEN_TIER_PREFERENCE] as const;
|
|
@ -6,16 +6,24 @@
|
|||
*/
|
||||
|
||||
import React, { type FC, useRef, useState, createContext, useMemo } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import { EuiSteps, EuiStepStatus } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||
import { UrlStateProvider } from '@kbn/ml-url-state';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import type { TransformConfigUnion } from '../../../../../../common/types/transform';
|
||||
|
||||
import { getCreateTransformRequestBody } from '../../../../common';
|
||||
import { SearchItems } from '../../../../hooks/use_search_items';
|
||||
import { useAppDependencies } from '../../../../app_dependencies';
|
||||
|
||||
import {
|
||||
applyTransformConfigToDefineState,
|
||||
|
@ -34,6 +42,10 @@ import {
|
|||
import { WizardNav } from '../wizard_nav';
|
||||
import type { RuntimeMappings } from '../step_define/common/types';
|
||||
|
||||
import { TRANSFORM_STORAGE_KEYS } from './storage';
|
||||
|
||||
const localStorage = new Storage(window.localStorage);
|
||||
|
||||
enum WIZARD_STEPS {
|
||||
DEFINE,
|
||||
DETAILS,
|
||||
|
@ -94,6 +106,7 @@ export const CreateTransformWizardContext = createContext<{
|
|||
});
|
||||
|
||||
export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems }) => {
|
||||
const appDependencies = useAppDependencies();
|
||||
const { dataView } = searchItems;
|
||||
|
||||
// The current WIZARD_STEP
|
||||
|
@ -113,7 +126,7 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
|
|||
const [stepCreateState, setStepCreateState] = useState(getDefaultStepCreateState);
|
||||
|
||||
const transformConfig = getCreateTransformRequestBody(
|
||||
dataView.getIndexPattern(),
|
||||
dataView,
|
||||
stepDefineState,
|
||||
stepDetailsState
|
||||
);
|
||||
|
@ -206,11 +219,24 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
|
|||
|
||||
const stepsConfig = [stepDefine, stepDetails, stepCreate];
|
||||
|
||||
const datePickerDeps = {
|
||||
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||
toMountPoint,
|
||||
wrapWithTheme,
|
||||
uiSettingsKeys: UI_SETTINGS,
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateTransformWizardContext.Provider
|
||||
value={{ dataView, runtimeMappings: stepDefineState.runtimeMappings }}
|
||||
>
|
||||
<EuiSteps className="transform__steps" steps={stepsConfig} />
|
||||
<UrlStateProvider>
|
||||
<StorageContextProvider storage={localStorage} storageKeys={TRANSFORM_STORAGE_KEYS}>
|
||||
<DatePickerContextProvider {...datePickerDeps}>
|
||||
<EuiSteps className="transform__steps" steps={stepsConfig} />
|
||||
</DatePickerContextProvider>
|
||||
</StorageContextProvider>
|
||||
</UrlStateProvider>
|
||||
</CreateTransformWizardContext.Provider>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import React, { useMemo, FC } from 'react';
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
||||
import { TransformConfigUnion } from '../../../../../../common/types/transform';
|
||||
|
||||
import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
|
||||
import { getPivotQuery } from '../../../../common';
|
||||
import { usePivotData } from '../../../../hooks/use_pivot_data';
|
||||
import { getTransformConfigQuery } from '../../../../common';
|
||||
import { useTransformConfigData } from '../../../../hooks/use_transform_config_data';
|
||||
import { SearchItems } from '../../../../hooks/use_search_items';
|
||||
|
||||
import {
|
||||
|
@ -38,15 +40,15 @@ export const ExpandedRowPreviewPane: FC<ExpandedRowPreviewPaneProps> = ({ transf
|
|||
[transformConfig]
|
||||
);
|
||||
|
||||
const pivotQuery = useMemo(() => getPivotQuery(searchQuery), [searchQuery]);
|
||||
const transformConfigQuery = useMemo(() => getTransformConfigQuery(searchQuery), [searchQuery]);
|
||||
|
||||
const dataViewTitle = Array.isArray(transformConfig.source.index)
|
||||
? transformConfig.source.index.join(',')
|
||||
: transformConfig.source.index;
|
||||
|
||||
const pivotPreviewProps = usePivotData(
|
||||
dataViewTitle,
|
||||
pivotQuery,
|
||||
const pivotPreviewProps = useTransformConfigData(
|
||||
{ getIndexPattern: () => dataViewTitle } as DataView,
|
||||
transformConfigQuery,
|
||||
validationStatus,
|
||||
previewRequest,
|
||||
runtimeMappings
|
||||
|
|
|
@ -46,6 +46,10 @@
|
|||
"@kbn/field-types",
|
||||
"@kbn/ml-nested-property",
|
||||
"@kbn/ml-is-defined",
|
||||
"@kbn/ml-date-picker",
|
||||
"@kbn/ml-url-state",
|
||||
"@kbn/ml-local-storage",
|
||||
"@kbn/ml-query-utils",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -112,6 +112,17 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
);
|
||||
await transform.sourceSelection.selectSource(ecIndexPattern);
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '10 Years ago'`);
|
||||
await transform.datePicker.quickSelect();
|
||||
|
||||
await transform.testExecution.logTestStep('loads the index preview');
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
await transform.testExecution.logTestStep('displays an empty transform preview');
|
||||
|
@ -191,6 +202,18 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'selects the source data and loads the Transform wizard page'
|
||||
);
|
||||
await transform.sourceSelection.selectSource(ecIndexPattern);
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '10 Years ago'`);
|
||||
await transform.datePicker.quickSelect();
|
||||
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
await transform.wizard.assertTransformPreviewEmpty();
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const canvasElement = getService('canvasElement');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const transform = getService('transform');
|
||||
const PageObjects = getPageObjects(['discover']);
|
||||
const pageObjects = getPageObjects(['discover']);
|
||||
|
||||
describe('creation_index_pattern', function () {
|
||||
before(async () => {
|
||||
|
@ -486,6 +486,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await transform.testExecution.logTestStep('has correct transform function selected');
|
||||
await transform.wizard.assertSelectedTransformFunction('pivot');
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`);
|
||||
await transform.datePicker.quickSelect();
|
||||
|
||||
await transform.testExecution.logTestStep('loads the index preview');
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
|
||||
|
@ -699,16 +710,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
await transform.testExecution.logTestStep('should navigate to discover');
|
||||
await transform.table.clickTransformRowAction(testData.transformId, 'Discover');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await pageObjects.discover.waitUntilSearchingHasFinished();
|
||||
|
||||
if (testData.discoverAdjustSuperDatePicker) {
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
await transform.discover.assertNoResults(testData.destinationIndex);
|
||||
await transform.testExecution.logTestStep(
|
||||
'should switch quick select lookback to years'
|
||||
);
|
||||
await transform.discover.assertSuperDatePickerToggleQuickMenuButtonExists();
|
||||
await transform.discover.openSuperDatePicker();
|
||||
await transform.discover.quickSelectYears();
|
||||
await transform.datePicker.quickSelect();
|
||||
}
|
||||
|
||||
await transform.discover.assertDiscoverQueryHits(testData.expected.discoverQueryHits);
|
||||
|
|
|
@ -279,6 +279,11 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await transform.testExecution.logTestStep('has correct transform function selected');
|
||||
await transform.wizard.assertSelectedTransformFunction('pivot');
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('has correct runtime mappings settings');
|
||||
await transform.wizard.assertRuntimeMappingsEditorSwitchExists();
|
||||
await transform.wizard.assertRuntimeMappingsEditorSwitchCheckState(false);
|
||||
|
@ -291,6 +296,12 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await transform.wizard.setRuntimeMappingsEditorContent(JSON.stringify(runtimeMappings));
|
||||
await transform.wizard.applyRuntimeMappings();
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`);
|
||||
await transform.datePicker.quickSelect(10, 'y');
|
||||
|
||||
await transform.testExecution.logTestStep('loads the index preview');
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
|
||||
|
@ -439,7 +450,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
if (isLatestTransformTestData(testData)) {
|
||||
const fromTime = 'Feb 7, 2016 @ 00:00:00.000';
|
||||
const toTime = 'Feb 11, 2016 @ 23:59:54.000';
|
||||
await transform.wizard.setDiscoverTimeRange(fromTime, toTime);
|
||||
await transform.datePicker.setTimeRange(fromTime, toTime);
|
||||
}
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
|
|
|
@ -144,6 +144,17 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await transform.testExecution.logTestStep('has correct transform function selected');
|
||||
await transform.wizard.assertSelectedTransformFunction('pivot');
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`);
|
||||
await transform.datePicker.quickSelect(10, 'y');
|
||||
|
||||
await transform.testExecution.logTestStep('loads the index preview');
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
|
||||
|
|
|
@ -418,6 +418,17 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
);
|
||||
}
|
||||
|
||||
await transform.testExecution.logTestStep(
|
||||
`sets the date picker to the default '15 minutes ago'`
|
||||
);
|
||||
await transform.datePicker.quickSelect(15, 'm');
|
||||
|
||||
await transform.testExecution.logTestStep('displays an empty index preview');
|
||||
await transform.wizard.assertIndexPreviewEmpty();
|
||||
|
||||
await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`);
|
||||
await transform.datePicker.quickSelect();
|
||||
|
||||
await transform.testExecution.logTestStep('should load the index preview');
|
||||
await transform.wizard.assertIndexPreviewLoaded();
|
||||
|
||||
|
|
|
@ -11,15 +11,15 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const security = getService('security');
|
||||
const PageObjects = getPageObjects(['common', 'settings', 'security']);
|
||||
const pageObjects = getPageObjects(['common', 'settings', 'security']);
|
||||
const appsMenu = getService('appsMenu');
|
||||
const managementMenu = getService('managementMenu');
|
||||
|
||||
describe('security', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await PageObjects.security.forceLogout();
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
await pageObjects.security.forceLogout();
|
||||
await pageObjects.common.navigateToApp('home');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -40,7 +40,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should not render the "Stack" section', async () => {
|
||||
await PageObjects.common.navigateToApp('management');
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
const sections = (await managementMenu.getSections()).map((section) => section.sectionId);
|
||||
expect(sections).to.eql(['insightsAndAlerting', 'kibana']);
|
||||
});
|
||||
|
@ -59,7 +59,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should render the "Data" section with Transform', async () => {
|
||||
await PageObjects.common.navigateToApp('management');
|
||||
await pageObjects.common.navigateToApp('management');
|
||||
const sections = await managementMenu.getSections();
|
||||
expect(sections).to.have.length(1);
|
||||
expect(sections[0]).to.eql({
|
||||
|
|
49
x-pack/test/functional/services/transform/date_picker.ts
Normal file
49
x-pack/test/functional/services/transform/date_picker.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function TransformDatePickerProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const find = getService('find');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pageObjects = getPageObjects(['timePicker']);
|
||||
|
||||
return {
|
||||
async assertSuperDatePickerToggleQuickMenuButtonExists() {
|
||||
await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton');
|
||||
},
|
||||
|
||||
async openSuperDatePicker() {
|
||||
await this.assertSuperDatePickerToggleQuickMenuButtonExists();
|
||||
await testSubjects.click('superDatePickerToggleQuickMenuButton');
|
||||
await testSubjects.existOrFail('superDatePickerQuickMenu');
|
||||
},
|
||||
|
||||
async quickSelect(timeValue: number = 15, timeUnit: string = 'y') {
|
||||
await this.openSuperDatePicker();
|
||||
const quickMenuElement = await testSubjects.find('superDatePickerQuickMenu');
|
||||
|
||||
// No test subject, defaults to select `"Years"` to look back 15 years instead of 15 minutes.
|
||||
await find.selectValue(`[aria-label*="Time value"]`, timeValue.toString());
|
||||
await find.selectValue(`[aria-label*="Time unit"]`, timeUnit);
|
||||
|
||||
// Apply
|
||||
const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton');
|
||||
const actualApplyButtonText = await applyButton.getVisibleText();
|
||||
expect(actualApplyButtonText).to.be('Apply');
|
||||
|
||||
await applyButton.click();
|
||||
await testSubjects.missingOrFail('superDatePickerQuickMenu');
|
||||
},
|
||||
|
||||
async setTimeRange(fromTime: string, toTime: string) {
|
||||
await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -10,7 +10,6 @@ import expect from '@kbn/expect';
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function TransformDiscoverProvider({ getService }: FtrProviderContext) {
|
||||
const find = getService('find');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
|
@ -28,6 +27,8 @@ export function TransformDiscoverProvider({ getService }: FtrProviderContext) {
|
|||
},
|
||||
|
||||
async assertNoResults(expectedDestinationIndex: string) {
|
||||
await testSubjects.missingOrFail('unifiedHistogramQueryHits');
|
||||
|
||||
// Discover should use the destination index pattern
|
||||
const actualIndexPatternSwitchLinkText = await (
|
||||
await testSubjects.find('discover-dataView-switch-link')
|
||||
|
@ -39,29 +40,5 @@ export function TransformDiscoverProvider({ getService }: FtrProviderContext) {
|
|||
|
||||
await testSubjects.existOrFail('discoverNoResults');
|
||||
},
|
||||
|
||||
async assertSuperDatePickerToggleQuickMenuButtonExists() {
|
||||
await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton');
|
||||
},
|
||||
|
||||
async openSuperDatePicker() {
|
||||
await testSubjects.click('superDatePickerToggleQuickMenuButton');
|
||||
await testSubjects.existOrFail('superDatePickerQuickMenu');
|
||||
},
|
||||
|
||||
async quickSelectYears() {
|
||||
const quickMenuElement = await testSubjects.find('superDatePickerQuickMenu');
|
||||
|
||||
// No test subject, select "Years" to look back 15 years instead of 15 minutes.
|
||||
await find.selectValue(`[aria-label*="Time unit"]`, 'y');
|
||||
|
||||
// Apply
|
||||
const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton');
|
||||
const actualApplyButtonText = await applyButton.getVisibleText();
|
||||
expect(actualApplyButtonText).to.be('Apply');
|
||||
|
||||
await applyButton.click();
|
||||
await testSubjects.existOrFail('unifiedHistogramQueryHits');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
|
||||
import { TransformAPIProvider } from './api';
|
||||
import { TransformEditFlyoutProvider } from './edit_flyout';
|
||||
import { TransformDatePickerProvider } from './date_picker';
|
||||
import { TransformDiscoverProvider } from './discover';
|
||||
import { TransformManagementProvider } from './management';
|
||||
import { TransformNavigationProvider } from './navigation';
|
||||
|
@ -25,6 +26,7 @@ import { MachineLearningTestResourcesProvider } from '../ml/test_resources';
|
|||
export function TransformProvider(context: FtrProviderContext) {
|
||||
const api = TransformAPIProvider(context);
|
||||
const mlApi = MachineLearningAPIProvider(context);
|
||||
const datePicker = TransformDatePickerProvider(context);
|
||||
const discover = TransformDiscoverProvider(context);
|
||||
const editFlyout = TransformEditFlyoutProvider(context);
|
||||
const management = TransformManagementProvider(context);
|
||||
|
@ -39,6 +41,7 @@ export function TransformProvider(context: FtrProviderContext) {
|
|||
|
||||
return {
|
||||
api,
|
||||
datePicker,
|
||||
discover,
|
||||
editFlyout,
|
||||
management,
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function TransformNavigationProvider({ getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const pageObjects = getPageObjects(['common']);
|
||||
|
||||
return {
|
||||
async navigateTo() {
|
||||
return await PageObjects.common.navigateToApp('transform');
|
||||
return await pageObjects.common.navigateToApp('transform');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,15 +12,15 @@ export function TransformSecurityUIProvider(
|
|||
{ getPageObjects }: FtrProviderContext,
|
||||
transformSecurityCommon: TransformSecurityCommon
|
||||
) {
|
||||
const PageObjects = getPageObjects(['security']);
|
||||
const pageObjects = getPageObjects(['security']);
|
||||
|
||||
return {
|
||||
async loginAs(user: USER) {
|
||||
const password = transformSecurityCommon.getPasswordForUser(user);
|
||||
|
||||
await PageObjects.security.forceLogout();
|
||||
await pageObjects.security.forceLogout();
|
||||
|
||||
await PageObjects.security.login(user, password, {
|
||||
await pageObjects.security.login(user, password, {
|
||||
expectSuccess: true,
|
||||
});
|
||||
},
|
||||
|
@ -34,7 +34,7 @@ export function TransformSecurityUIProvider(
|
|||
},
|
||||
|
||||
async logout() {
|
||||
await PageObjects.security.forceLogout();
|
||||
await pageObjects.security.forceLogout();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
|
|||
const ml = getService('ml');
|
||||
const toasts = getService('toasts');
|
||||
|
||||
const PageObjects = getPageObjects(['discover', 'timePicker']);
|
||||
const pageObjects = getPageObjects(['discover', 'timePicker']);
|
||||
|
||||
return {
|
||||
async clickNextButton() {
|
||||
|
@ -80,6 +80,10 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
|
|||
await testSubjects.existOrFail(selector);
|
||||
},
|
||||
|
||||
async assertIndexPreviewEmpty() {
|
||||
await this.assertIndexPreviewExists('empty');
|
||||
},
|
||||
|
||||
async assertIndexPreviewLoaded() {
|
||||
await this.assertIndexPreviewExists('loaded');
|
||||
},
|
||||
|
@ -995,19 +999,14 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
|
|||
async redirectToDiscover() {
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.click('transformWizardCardDiscover');
|
||||
await PageObjects.discover.isDiscoverAppOnScreen();
|
||||
await pageObjects.discover.isDiscoverAppOnScreen();
|
||||
});
|
||||
},
|
||||
|
||||
async setDiscoverTimeRange(fromTime: string, toTime: string) {
|
||||
await PageObjects.discover.isDiscoverAppOnScreen();
|
||||
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
|
||||
},
|
||||
|
||||
async assertDiscoverContainField(field: string) {
|
||||
await PageObjects.discover.isDiscoverAppOnScreen();
|
||||
await pageObjects.discover.isDiscoverAppOnScreen();
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
const allFields = await PageObjects.discover.getAllFieldNames();
|
||||
const allFields = await pageObjects.discover.getAllFieldNames();
|
||||
if (Array.isArray(allFields)) {
|
||||
// For some reasons, Discover returns fields with dot (e.g '.avg') with extra space
|
||||
const fields = allFields.map((n) => n.replace('.', '.'));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue